import { EnumerableStorage } from '@overworld-engine/core'; import { ReactNode } from 'react'; import { ReconcilerRoot, RenderProps } from '@react-three/fiber'; import { MovementInputRef } from '@overworld-engine/input'; import { PlatformBridge } from '@overworld-engine/platform'; /** * Minimal structural typings for the WeChat `wx` global — exactly the * surface this package consumes, nothing more. Declared here (instead of * depending on `@types/wechat-miniprogram` etc.) so the package has zero * type-level dependencies on WeChat SDKs and the same shapes double as the * contract for test fakes (`vi.stubGlobal('wx', fake)`). * * Members that only exist in **mini-games** (canvas, global touch events) * are optional; the adapters that need them throw a helpful error when * they are missing (e.g. when running in a WXML mini-program). */ /** Result of `wx.getStorageInfoSync()`. */ interface WxStorageInfo { /** All keys currently present in `wx` storage. */ keys: string[]; } /** Result of `wx.getSystemInfoSync()` (subset). */ interface WxSystemInfo { /** Viewport width in CSS px. */ windowWidth: number; /** Viewport height in CSS px. */ windowHeight: number; /** Device pixel ratio. */ pixelRatio: number; /** Safe area in CSS px, when the device reports one (notches etc.). */ safeArea?: { top: number; right: number; bottom: number; left: number; width: number; height: number; }; } /** One touch point in a WeChat touch event. */ interface WxTouch { identifier: number; clientX: number; clientY: number; } /** Payload of `wx.onTouchStart/Move/End/Cancel` callbacks. */ interface WxTouchEvent { touches: WxTouch[]; changedTouches: WxTouch[]; } /** Listener for WeChat touch events. */ type WxTouchListener = (event: WxTouchEvent) => void; /** The `SocketTask` returned by `wx.connectSocket` (subset). */ interface WxSocketTask { send(options: { data: string; }): void; close(options?: { code?: number; reason?: string; }): void; onOpen(callback: () => void): void; onMessage(callback: (result: { data: string | ArrayBuffer; }) => void): void; onClose(callback: () => void): void; onError(callback: (error: unknown) => void): void; } /** The `InnerAudioContext` returned by `wx.createInnerAudioContext` (subset). */ interface WxInnerAudioContext { src: string; loop: boolean; volume: number; /** Whether playback is currently paused or stopped (read-only in wx). */ readonly paused: boolean; play(): void; pause(): void; stop(): void; destroy(): void; onEnded(callback: () => void): void; offEnded(callback: () => void): void; } /** The canvas returned by `wx.createCanvas()` in mini-games (subset). */ interface WxCanvas { width: number; height: number; getContext(contextId: string): unknown; } /** * Structural interface for the parts of the WeChat `wx` global used by * `@overworld-engine/adapters-weapp`. */ interface Wx { getStorageSync(key: string): unknown; setStorageSync(key: string, value: unknown): void; removeStorageSync(key: string): void; getStorageInfoSync(): WxStorageInfo; connectSocket(options: { url: string; protocols?: string[]; }): WxSocketTask; createInnerAudioContext(): WxInnerAudioContext; getSystemInfoSync(): WxSystemInfo; onShow(callback: () => void): void; onHide(callback: () => void): void; offShow?(callback: () => void): void; offHide?(callback: () => void): void; createCanvas?(): WxCanvas; onTouchStart?(callback: WxTouchListener): void; onTouchMove?(callback: WxTouchListener): void; onTouchEnd?(callback: WxTouchListener): void; onTouchCancel?(callback: WxTouchListener): void; offTouchStart?(callback: WxTouchListener): void; offTouchMove?(callback: WxTouchListener): void; offTouchEnd?(callback: WxTouchListener): void; offTouchCancel?(callback: WxTouchListener): void; } /** * The `wx` global, structurally typed. * * @throws outside a WeChat environment (no `wx` global) with a hint that * these adapters only run inside WeChat mini-games / mini-programs. */ declare function getWx(): Wx; /** * WeChat storage as core's `EnumerableStorage`: feed it to * `persistOptions({ storage: ... })` and `createSaveSlots({ storage })` * exactly like a wrapped `localStorage`. */ /** * Create an {@link EnumerableStorage} over the synchronous `wx` storage * APIs (`getStorageSync` / `setStorageSync` / `removeStorageSync`, keys * enumerated via `getStorageInfoSync().keys`). * * Note: `wx.getStorageSync` returns `''` for missing keys, so the empty * string is not representable and reads as `null` — irrelevant in practice, * because the persistence layer only ever stores JSON documents. Non-string * values written by other code also read as `null`. * * @throws outside a WeChat environment (see `getWx`). */ declare function createWeappStorage(): EnumerableStorage; /** Standard WebSocket readyState values (mirrored by this wrapper). */ declare const WS_CONNECTING = 0; declare const WS_OPEN = 1; declare const WS_CLOSING = 2; declare const WS_CLOSED = 3; /** * A minimal `WebSocket` implementation backed by `wx.connectSocket`. * Matches net's structural `WebSocketLike`/`WebSocketConstructor` * (readyState, `send(string)`, `close()`, `onopen`/`onmessage`/`onclose`/ * `onerror` handler properties). * * @throws outside a WeChat environment (see `getWx`). */ declare class WeappWebSocket { readyState: number; onopen: (() => void) | null; onmessage: ((event: { data: unknown; }) => void) | null; onclose: (() => void) | null; onerror: ((event: unknown) => void) | null; private task; constructor(url: string, protocols?: string | string[]); send(data: string): void; close(code?: number, reason?: string): void; } /** * One playable audio source. Structurally identical to * `@overworld-engine/audio`'s `AudioHandle`. */ interface AudioHandle { /** Start (or resume) playback. */ play(): Promise | void; /** Pause playback, keeping the current position. */ pause(): void; /** Set the playback volume (0–1). */ setVolume(volume: number): void; /** Current playback volume. */ getVolume(): number; /** Enable/disable looping. */ setLoop(loop: boolean): void; /** Whether the source is currently paused (or never started). */ isPaused(): boolean; /** Subscribe to "playback finished" (never fired while looping). Returns unbind. */ onEnded(callback: () => void): () => void; /** Release the source. The handle must not be used afterwards. */ destroy(): void; } /** * Creates {@link AudioHandle}s. Structurally identical to * `@overworld-engine/audio`'s `AudioBackend`. */ interface AudioBackend { create(url: string): AudioHandle; isAvailable?(): boolean; } /** * Audio backend over `wx.createInnerAudioContext()` — one context per * handle; `destroy()` releases the native resource (the audio manager does * this automatically for finished SFX one-shots and replaced BGM). * * ```ts * const audio = createAudioManager({ tracks, backend: createWeappAudioBackend() }) * ``` */ declare function createWeappAudioBackend(): AudioBackend; /** * Full 3D on WeChat mini-games: an R3F root on `wx.createCanvas()` via * `@react-three/fiber`'s low-level `createRoot` API (no react-dom, no DOM * ``), sized from `wx.getSystemInfoSync()`. * * Requires the official `weapp-adapter` polyfill to be loaded first (it * provides the `window`/`document`/RAF/XHR shims three.js and React's * scheduler rely on) — see the weapp-game template. * * **Pointer events:** the root is configured with `events: undefined`, so out * of the box no scene-graph pointer events fire (byte-identical to v1.1) — * games that only move via `createWeappTouchJoystick` and interact via * proximity + `interact()` need nothing more. To enable `onClick` / * `onPointerOver` / raycast picking on meshes, attach * `createWeappPointerBridge(canvasRoot)` after `render()`; it drives fiber's * pointer pipeline from `wx` touches (see `pointerEvents.ts`). The bridge * reads {@link WeappCanvasRoot.store}, which `render()` populates. */ /** Resolved canvas size: CSS-pixel dimensions plus device pixel ratio. */ interface CanvasRootSize { /** Width in CSS px (`windowWidth`). */ width: number; /** Height in CSS px (`windowHeight`). */ height: number; /** Device pixel ratio, clamped to `[1, 2]`. */ dpr: number; } /** DPR is clamped to this maximum: >2 costs fill rate for invisible gains. */ declare const MAX_CANVAS_DPR = 2; /** * Pure sizing math for {@link createWeappCanvasRoot}: window size from * system info, dpr = `dprOverride ?? pixelRatio` clamped to `[1, 2]`. */ declare function computeCanvasRootSize(info: { windowWidth: number; windowHeight: number; pixelRatio: number; }, dprOverride?: number): CanvasRootSize; /** The type `createRoot` must satisfy — also the test seam. */ type CreateRootFn = (canvas: HTMLCanvasElement) => ReconcilerRoot; /** Options for {@link createWeappCanvasRoot}. */ interface WeappCanvasRootOptions { /** Render canvas. Defaults to `wx.createCanvas()` (the on-screen canvas). */ canvas?: WxCanvas; /** Device pixel ratio override. Defaults to `getSystemInfoSync().pixelRatio`, clamped to 2. */ dpr?: number; /** * Extra R3F render props merged **over** the defaults (`gl` antialias, * size, dpr, `frameloop: 'always'`, no events) — e.g. `camera`, * `shadows`, `onCreated`. */ renderProps?: RenderProps; /** Test seam: replaces fiber's `createRoot`. */ createRootImpl?: CreateRootFn; } /** The R3F store `root.render()` returns (the seam `createWeappPointerBridge` reads). */ type R3FStore = ReturnType['render']>; /** Handle returned by {@link createWeappCanvasRoot}. */ interface WeappCanvasRoot { /** The configured R3F root (call `root.configure(...)` again to reconfigure). */ root: ReconcilerRoot; /** The WeChat canvas being rendered to. */ canvas: WxCanvas; /** Resolved size/dpr the root was configured with. */ size: CanvasRootSize; /** * The R3F store, available after the first {@link WeappCanvasRoot.render} * (null before). `createWeappPointerBridge` uses it to install the pointer * event layer. */ store: R3FStore | null; /** Render a React element tree into the root (re-render by calling again). */ render(node: ReactNode): void; /** Unmount the tree and release the root. Idempotent. */ dispose(): void; } declare function createWeappCanvasRoot(options?: WeappCanvasRootOptions): WeappCanvasRoot; /** * DOM-less virtual joystick for WeChat mini-games: consumes global `wx` * touch events directly (there is no DOM to attach `` to) * and writes the exact same normalized movement vector into a * `MovementInputRef` — hand that ref to `` and * movement works like on the web. * * The stick is **anchored at the touch-start point** (a "floating" * joystick): the first touch in the configured region becomes the center, * dragging away from it deflects the stick, releasing resets to neutral. * All the math is `@overworld-engine/input`'s pure joystick functions, so * dead zone and run threshold behave identically to ``. */ /** Options for {@link createWeappTouchJoystick}. */ interface WeappTouchJoystickOptions { /** * Which touches grab the joystick: `'left-half'` (default) only reacts to * touches starting on the left half of the screen (leaving the right half * for interact buttons), `'full'` reacts anywhere. */ region?: 'left-half' | 'full'; /** * Virtual stick diameter in px — full deflection is reached `size / 2` * px away from the anchor. Default: 120 (matches ``). */ size?: number; /** Deflections below this magnitude read as no input. Default: 0.15. */ deadZone?: number; /** Deflections at/above this magnitude set `running: true`. Default: 0.85. */ runThreshold?: number; } /** Handle returned by {@link createWeappTouchJoystick}. */ interface WeappTouchJoystick { /** Unbind all touch listeners and reset the target to neutral. */ dispose(): void; } /** * Subscribe to `wx.onTouchStart/Move/End/Cancel` and drive `target`: * * ```ts * const movement = createMovementInput() * const joystick = createWeappTouchJoystick(movement) * // in the R3F tree * ``` * * @throws when the global `wx` touch APIs are missing (WeChat *mini-game* * only; mini-programs receive touches through WXML instead). */ declare function createWeappTouchJoystick(target: MovementInputRef, options?: WeappTouchJoystickOptions): WeappTouchJoystick; /** CSS-pixel size the NDC math needs (a subset of R3F's `state.size`). */ interface PointerSize { width: number; height: number; } /** Canvas top-left in CSS px. Fullscreen wx canvas → `{ left: 0, top: 0 }`. */ interface CanvasOrigin { left: number; top: number; } /** * Map a `wx` touch (CSS-px `clientX/clientY`) to canvas-relative offset px. * For a fullscreen wx canvas the origin is `{ 0, 0 }`, so offset === client. */ declare function touchToOffset(touch: { clientX: number; clientY: number; }, origin?: CanvasOrigin): { offsetX: number; offsetY: number; }; /** * Convert a canvas-relative offset (px, top-left origin) to normalized device * coordinates in `[-1, 1]` (x right, y up) — the exact mapping fiber's default * `compute` uses: `x = offsetX / width * 2 - 1`, `y = -(offsetY / height) * 2 + 1`. */ declare function offsetToNdc(offsetX: number, offsetY: number, size: PointerSize): { x: number; y: number; }; /** Convenience: `wx` touch → NDC in one step (fullscreen origin by default). */ declare function touchToNdc(touch: { clientX: number; clientY: number; }, size: PointerSize, origin?: CanvasOrigin): { x: number; y: number; }; /** Options for {@link createWeappPointerBridge}. */ interface WeappPointerBridgeOptions { /** * Which taps drive picking: `'full'` (default) picks anywhere, `'left-half'` * / `'right-half'` confine picking to one half of the screen so it can * hard-partition against a joystick that owns the other half. (By default no * partition is needed — the tap-vs-drag rule already keeps a joystick drag * from registering as a click.) */ region?: 'full' | 'left-half' | 'right-half'; /** Max ms between touch-start and touch-end to count as a tap → `onClick`. Default 400. */ tapMaxDurationMs?: number; /** Max CSS-px travel between touch-start and touch-end to count as a tap. Default 12. */ tapMaxDistance?: number; /** Canvas top-left in CSS px (offset math). Default `{ left: 0, top: 0 }` (fullscreen). */ canvasOrigin?: CanvasOrigin; } /** Handle returned by {@link createWeappPointerBridge}. */ interface WeappPointerBridge { /** * Unbind all `wx` touch listeners and disable the event layer (raycasting * stops, mesh handlers no longer fire). Idempotent. */ dispose(): void; } /** * Wire R3F pointer events on a WeChat mini-game canvas, fed entirely by `wx` * touch events. Call it **after** the first `render()` (the R3F store must * exist): * * ```ts * const canvasRoot = createWeappCanvasRoot({ renderProps: { camera } }) * canvasRoot.render() * const bridge = createWeappPointerBridge(canvasRoot) // onClick etc. now fire * // ...later: bridge.dispose() * ``` * * The `` / `` you author in the scene now receive * real raycast hits from taps. Movement stays with * {@link createWeappTouchJoystick}; see the module docs for how the two share * touches. * * @throws when `root.store` is null (call `render()` first) or when the `wx` * global touch APIs are missing (WeChat *mini-game* only). */ declare function createWeappPointerBridge(root: { store: R3FStore | null; canvas: WxCanvas; }, options?: WeappPointerBridgeOptions): WeappPointerBridge; /** * Build the weapp {@link PlatformBridge}: `wx` storage for saves, * `onShow`/`onHide` lifecycle, safe area from `getSystemInfoSync()`. * `openExternal` is a warn-only no-op — WeChat mini-games cannot open * external browsers. * * Usually not called directly — use {@link registerWeappBridge} and let * platform's `createBridge()` construct it. */ declare function createWeappBridge(): PlatformBridge; /** * Register the weapp bridge with `@overworld-engine/platform`, so * `createBridge()` (and `createBridge('weapp')`) returns it inside WeChat: * * ```ts * import { registerWeappBridge } from '@overworld-engine/adapters-weapp' * import { createBridge } from '@overworld-engine/platform' * * registerWeappBridge() * const bridge = createBridge() // kind === 'weapp' inside WeChat * ``` */ declare function registerWeappBridge(): void; export { type AudioBackend, type AudioHandle, type CanvasOrigin, type CanvasRootSize, type CreateRootFn, MAX_CANVAS_DPR, type PointerSize, type R3FStore, WS_CLOSED, WS_CLOSING, WS_CONNECTING, WS_OPEN, type WeappCanvasRoot, type WeappCanvasRootOptions, type WeappPointerBridge, type WeappPointerBridgeOptions, type WeappTouchJoystick, type WeappTouchJoystickOptions, WeappWebSocket, type Wx, type WxCanvas, type WxInnerAudioContext, type WxSocketTask, type WxStorageInfo, type WxSystemInfo, type WxTouch, type WxTouchEvent, type WxTouchListener, computeCanvasRootSize, createWeappAudioBackend, createWeappBridge, createWeappCanvasRoot, createWeappPointerBridge, createWeappStorage, createWeappTouchJoystick, getWx, offsetToNdc, registerWeappBridge, touchToNdc, touchToOffset };