import * as _energy8platform_shell_html from '@energy8platform/shell/html'; import { Container, ApplicationOptions, Application } from 'pixi.js'; import { CasinoGameSDK, InitData, GameConfigData, SessionData } from '@energy8platform/game-sdk'; import { LoadingScreenConfig, AssetManifest, PlatformSession } from '@energy8platform/platform-core'; declare enum ScaleMode { /** Fit inside container, maintain aspect ratio (letterbox/pillarbox) */ FIT = "FIT", /** Fill container, maintain aspect ratio (crop edges) */ FILL = "FILL", /** Stretch to fill (distorts) */ STRETCH = "STRETCH" } declare enum Orientation { LANDSCAPE = "landscape", PORTRAIT = "portrait", ANY = "any" } interface AudioConfig { /** Default volumes per category (0..1) */ music?: number; sfx?: number; ui?: number; ambient?: number; /** Persist mute state in localStorage */ persist?: boolean; /** LocalStorage key prefix */ storageKey?: string; } interface GameApplicationConfig { /** Container element or CSS selector to mount canvas into */ container?: HTMLElement | string; /** Reference design width (fallback: GameConfigData.viewport.width or 1920) */ designWidth?: number; /** Reference design height (fallback: GameConfigData.viewport.height or 1080) */ designHeight?: number; /** How to scale the game to fit the container */ scaleMode?: ScaleMode; /** Preferred orientation */ orientation?: Orientation; /** Loading screen configuration */ loading?: LoadingScreenConfig; /** Asset manifest — what to load */ manifest?: AssetManifest; /** Audio configuration */ audio?: AudioConfig; /** SDK options. Set to false to disable SDK (offline/development mode) */ sdk?: { parentOrigin?: string; timeout?: number; debug?: boolean; /** Use in-memory channel instead of postMessage (no iframe required) */ devMode?: boolean; } | false; /** PixiJS Application options (pass-through) */ pixi?: Partial; /** Enable debug overlay (FPS, draw calls) */ debug?: boolean; /** When set, GameApplication mounts the branded game shell after the SDK handshake. */ shell?: _energy8platform_shell_html.ShellConfig | false; } interface SceneConstructor { new (): IScene; [key: string]: any; } interface IScene { /** Root display container for this scene */ readonly container: Container; /** @internal GameApplication reference — set by SceneManager */ __engineApp?: any; /** Called when the scene is entered */ onEnter?(data?: unknown): Promise | void; /** Called when the scene is exited */ onExit?(): Promise | void; /** Called every frame */ onUpdate?(dt: number): void; /** Called when viewport resizes */ onResize?(width: number, height: number): void; /** Called when the scene is destroyed */ onDestroy?(): void; } declare enum TransitionType { NONE = "none", FADE = "fade", SLIDE_LEFT = "slide-left", SLIDE_RIGHT = "slide-right" } interface TransitionConfig { type: TransitionType; duration?: number; easing?: (t: number) => number; } interface GameEngineEvents { /** Fired when engine initialization is complete */ initialized: void; /** Fired when all assets are loaded */ loaded: void; /** Fired when the engine starts running */ started: void; /** Fired on viewport resize */ resize: { width: number; height: number; }; /** Fired on orientation change */ orientationChange: Orientation; /** Fired on scene change */ sceneChange: { from: string | null; to: string; }; /** Fired when player balance changes (forwarded from SDK) */ balanceUpdate: { balance: number; }; /** Fired on error */ error: Error; /** Fired when engine is destroyed */ destroyed: void; } /** * Minimal typed event emitter. * Used internally by GameApplication, SceneManager, AudioManager, etc. * * Supports `void` event types — events that carry no data can be emitted * without arguments: `emitter.emit('eventName')`. */ declare class EventEmitter { private listeners; on(event: K, handler: (data: TEvents[K]) => void): this; once(event: K, handler: (data: TEvents[K]) => void): this; off(event: K, handler: (data: TEvents[K]) => void): this; emit(...args: TEvents[K] extends void ? [event: K] : [event: K, data: TEvents[K]]): void; removeAllListeners(event?: keyof TEvents): this; } interface SceneEntry { scene: IScene; key: string; } interface SceneManagerEvents { change: { from: string | null; to: string; }; } /** * Manages the scene stack and transitions between scenes. * * @example * ```ts * const scenes = new SceneManager(app.stage); * scenes.register('loading', LoadingScene); * scenes.register('game', GameScene); * await scenes.goto('loading'); * ``` */ declare class SceneManager extends EventEmitter { private static MAX_TRANSITION_DEPTH; /** Root container that scenes are added to */ root: Container; private registry; private stack; private _transitionDepth; /** Current viewport dimensions — set by ViewportManager */ private _width; private _height; /** @internal GameApplication reference — passed to scenes */ private _app; constructor(root?: Container); /** @internal Set the root container (called by GameApplication after PixiJS init) */ setRoot(root: Container): void; /** @internal Set the app reference (called by GameApplication) */ setApp(app: any): void; /** Register a scene class by key */ register(key: string, ctor: SceneConstructor): this; /** Get the current (topmost) scene entry */ get current(): SceneEntry | null; /** Get the current scene key */ get currentKey(): string | null; /** Whether a scene transition is in progress */ get isTransitioning(): boolean; /** * Navigate to a scene, replacing the entire stack. */ goto(key: string, data?: unknown, transition?: TransitionConfig): Promise; /** * Push a scene onto the stack (the previous scene stays underneath). * Useful for overlays, modals, pause screens. */ push(key: string, data?: unknown, transition?: TransitionConfig): Promise; /** * Pop the top scene from the stack. */ pop(transition?: TransitionConfig): Promise; /** * Replace the top scene with a new one. */ replace(key: string, data?: unknown, transition?: TransitionConfig): Promise; /** * Called every frame by GameApplication. */ update(dt: number): void; /** * Called on viewport resize. */ resize(width: number, height: number): void; /** * Destroy all scenes and clear the manager. */ destroy(): void; private createScene; private pushInternal; private popInternal; private transitionIn; private transitionOut; } /** * Manages game asset loading with progress tracking, bundle support, and * automatic base path resolution from SDK's assetsUrl. * * Wraps PixiJS Assets API with a typed, game-oriented interface. * * @example * ```ts * const assets = new AssetManager('https://cdn.example.com/game/', manifest); * await assets.init(); * await assets.loadBundle('preload', (p) => console.log(p)); * const texture = assets.get('hero'); * ``` */ declare class AssetManager { private _initialized; private _basePath; private _manifest; private _loadedBundles; constructor(basePath?: string, manifest?: AssetManifest); /** Whether the asset system has been initialized */ get initialized(): boolean; /** Base path for all assets (usually from SDK's assetsUrl) */ get basePath(): string; /** Set of loaded bundle names */ get loadedBundles(): ReadonlySet; /** * Initialize the asset system. * Must be called before loading any assets. */ init(): Promise; /** * Load a single bundle by name. * * @param name - Bundle name (must exist in the manifest) * @param onProgress - Progress callback (0..1) * @returns Loaded assets map */ loadBundle(name: string, onProgress?: (progress: number) => void): Promise>; /** * Load multiple bundles simultaneously. * Progress is aggregated across all bundles. * * @param names - Bundle names * @param onProgress - Progress callback (0..1) */ loadBundles(names: string[], onProgress?: (progress: number) => void): Promise>; /** * Load individual assets by URL or alias. * * @param urls - Asset URLs or aliases * @param onProgress - Progress callback (0..1) */ load(urls: string | string[], onProgress?: (progress: number) => void): Promise; /** * Get a loaded asset synchronously from cache. * * @param alias - Asset alias * @throws if not loaded */ get(alias: string): T; /** * Unload a bundle to free memory. */ unloadBundle(name: string): Promise; /** * Start background loading a bundle (low-priority preload). * Useful for loading bonus round assets while player is in base game. */ backgroundLoad(name: string): Promise; /** * Get all bundle names from the manifest. */ getBundleNames(): string[]; /** * Check if a bundle is loaded. */ isBundleLoaded(name: string): boolean; private ensureInitialized; } type AudioCategoryName = 'music' | 'sfx' | 'ui' | 'ambient'; /** * Manages all game audio: music, SFX, UI sounds, ambient. * * Optional dependency on @pixi/sound — if not installed, AudioManager * operates as a silent no-op (graceful degradation). * * Features: * - Per-category volume control (music, sfx, ui, ambient) * - Music crossfade and looping * - Mobile audio unlock on first interaction * - Mute state persistence in localStorage * - Global mute/unmute * * @example * ```ts * const audio = new AudioManager({ music: 0.5, sfx: 0.8 }); * await audio.init(); * audio.playMusic('bg-music'); * audio.play('spin-click', 'sfx'); * ``` */ declare class AudioManager { private _soundModule; private _initialized; private _globalMuted; private _persist; private _storageKey; private _categories; private _masterGain; private _currentMusic; /** Duck factor (0..1) from duckMusic/unduckMusic. A presentation state, not a player setting. */ private _musicDuck; /** Crossfade ramp (0..1) for the track that is fading IN. 1 whenever no fade is running. */ private _musicFade; /** Generation counter so a superseded crossfade ramp stops writing over the new track's. */ private _musicFadeToken; private _unlocked; private _unlockHandler; constructor(config?: AudioConfig); /** Whether the audio system is initialized */ get initialized(): boolean; /** Whether audio is globally muted */ get muted(): boolean; /** * Initialize the audio system. * Dynamically imports @pixi/sound to keep it optional. */ init(): Promise; /** * Play a sound effect. * * @param alias - Sound alias (must be loaded via AssetManager) * @param category - Audio category (default: 'sfx') * @param options - Additional play options */ play(alias: string, category?: AudioCategoryName, options?: { volume?: number; loop?: boolean; speed?: number; }): void; /** * Play background music with optional crossfade. * * @param alias - Music alias * @param fadeDuration - Crossfade duration in ms (default: 500) */ playMusic(alias: string, fadeDuration?: number): void; /** * Stop current music. */ stopMusic(): void; /** * Stop all sounds. */ stopAll(): void; /** Global gain (0..1) folded into every category's effective volume. Driven by the shell's * 'master' settingChange. Does not affect the persisted per-category volumes. */ setMasterVolume(volume: number): void; getMasterVolume(): number; /** * Set volume for a category. */ setVolume(category: AudioCategoryName, volume: number): void; /** * Get volume for a category. */ getVolume(category: AudioCategoryName): number; /** * Mute a specific category. */ muteCategory(category: AudioCategoryName): void; /** * Unmute a specific category. */ unmuteCategory(category: AudioCategoryName): void; /** * Toggle mute for a category. */ toggleCategory(category: AudioCategoryName): boolean; /** * Mute all audio globally. */ muteAll(): void; /** * Unmute all audio globally. */ unmuteAll(): void; /** * Toggle global mute. */ toggleMute(): boolean; /** * Duck music volume (e.g., during big win presentation). * * @param factor - Volume multiplier (0..1), e.g. 0.3 = 30% of normal */ duckMusic(factor: number): void; /** * Restore music to normal volume after ducking. */ unduckMusic(): void; /** * Destroy the audio manager and free resources. */ destroy(): void; /** * Smoothly fade a sound's volume from `fromVol` to `toVol` over `durationMs`. */ private fadeVolume; /** * The SOUND-layer gain for the running music track. * * @pixi/sound resolves a playing instance as `instance × sound × global` (WebAudioInstance. * refresh). Each of those three has exactly ONE owner here, which is what keeps the mixer honest: * global — the master gain (`applyVolumes`) * sound — music: this function. sfx: untouched, left at 1. * instance — sfx: the per-call volume × the sfx category. music: always 1. * Everything that can move music volume — the player's slider, the category mute, a big-win duck, * a crossfade — is a term below, so they compose instead of overwriting each other. */ private musicGain; /** Push `musicGain()` at the current track. Safe before it starts playing and with none playing. */ private applyMusicGain; /** Current SOUND-layer volume of `alias`, or 0 when it cannot be read. */ private soundVolumeOf; /** Ramp the crossfade term 0 → 1 over `durationMs`, recomposing the gain each frame so a slider * drag or a duck landing mid-fade is honoured rather than overwritten when the fade ends. */ private rampMusicFade; private applyVolumes; private setupMobileUnlock; private removeMobileUnlock; private saveState; private restoreState; } interface InputEvents { tap: { x: number; y: number; }; press: { x: number; y: number; }; release: { x: number; y: number; }; move: { x: number; y: number; }; swipe: { direction: 'up' | 'down' | 'left' | 'right'; velocity: number; }; keydown: { key: string; code: string; }; keyup: { key: string; code: string; }; } /** * Unified input manager for touch, mouse, and keyboard. * * Features: * - Unified pointer events (works with touch + mouse) * - Swipe gesture detection * - Keyboard input with isKeyDown state * - Input locking (block input during animations) * * @example * ```ts * const input = new InputManager(app.canvas); * * input.on('tap', ({ x, y }) => console.log('Tapped at', x, y)); * input.on('swipe', ({ direction }) => console.log('Swiped', direction)); * input.on('keydown', ({ key }) => { * if (key === ' ') spin(); * }); * * // Block input during animations * input.lock(); * await playAnimation(); * input.unlock(); * ``` */ declare class InputManager extends EventEmitter { private _canvas; private _locked; private _keysDown; private _destroyed; private _viewportScale; private _viewportOffsetX; private _viewportOffsetY; private _pointerStart; private _swipeThreshold; private _swipeMaxTime; constructor(canvas: HTMLCanvasElement); /** Whether input is currently locked */ get locked(): boolean; /** Lock all input (e.g., during animations) */ lock(): void; /** Unlock input */ unlock(): void; /** Check if a key is currently pressed */ isKeyDown(key: string): boolean; /** * Update the viewport transform used for DOM→world coordinate mapping. * Called automatically by GameApplication when ViewportManager emits resize. */ setViewportTransform(scale: number, offsetX: number, offsetY: number): void; /** * Convert a DOM canvas position to game-world coordinates, * accounting for viewport scaling and offset. */ getWorldPosition(canvasX: number, canvasY: number): { x: number; y: number; }; /** Destroy the input manager */ destroy(): void; private setupPointerEvents; private onPointerDown; private onPointerUp; private onPointerMove; private getCanvasPosition; private setupKeyboardEvents; private onKeyDown; private onKeyUp; } interface ViewportConfig { designWidth: number; designHeight: number; scaleMode: ScaleMode; orientation: Orientation; } interface ViewportEvents { resize: { width: number; height: number; scale: number; }; orientationChange: Orientation; } /** * Manages responsive scaling of the game canvas to fit its container. * * Supports three scale modes: * - **FIT** — letterbox/pillarbox to maintain aspect ratio (industry standard) * - **FILL** — fill container, crop edges * - **STRETCH** — stretch to fill (distorts) * * Also handles: * - Orientation detection (landscape/portrait) * - Safe areas (mobile notch) * - ResizeObserver for smooth container resizing * * @example * ```ts * const viewport = new ViewportManager(app, container, { * designWidth: 1920, * designHeight: 1080, * scaleMode: ScaleMode.FIT, * orientation: Orientation.LANDSCAPE, * }); * * viewport.on('resize', ({ width, height, scale }) => { * console.log(`New size: ${width}x${height} @ ${scale}x`); * }); * ``` */ declare class ViewportManager extends EventEmitter { private _app; private _container; private _config; private _target; private _resizeObserver; private _currentOrientation; private _currentWidth; private _currentHeight; private _currentScale; private _destroyed; private _resizeTimeout; constructor(app: Application, container: HTMLElement, config: ViewportConfig, target?: Container); /** Current canvas width in game units */ get width(): number; /** Current canvas height in game units */ get height(): number; /** Current scale factor */ get scale(): number; /** Current orientation */ get orientation(): Orientation; /** Design reference width */ get designWidth(): number; /** Design reference height */ get designHeight(): number; /** * Force a resize calculation. Called automatically on container size change. */ refresh(): void; /** * Destroy the viewport manager. */ destroy(): void; private setupObserver; private onWindowResize; private debouncedRefresh; } /** * FPS overlay for debugging performance. * * Shows FPS, frame time, and draw call count in the corner of the screen. * * @example * ```ts * const fps = new FPSOverlay(app); * fps.show(); * ``` */ declare class FPSOverlay { private _app; private _container; private _fpsText; private _visible; private _samples; private _maxSamples; private _lastUpdate; private _tickFn; constructor(app: Application); /** Show the FPS overlay */ show(): void; /** Hide the FPS overlay */ hide(): void; /** Toggle visibility */ toggle(): void; /** Destroy the overlay */ destroy(): void; } /** * The main entry point for a game built on @energy8platform/game-engine. * * Orchestrates the full lifecycle: * 1. Create PixiJS Application * 2. Initialize SDK (or run offline) * 3. Show CSS preloader → Canvas loading screen with progress bar * 4. Load asset manifest * 5. Transition to the first game scene * * @example * ```ts * import { GameApplication, ScaleMode } from '@energy8platform/game-engine'; * import { GameScene } from './scenes/GameScene'; * * const game = new GameApplication({ * container: '#game', * designWidth: 1920, * designHeight: 1080, * scaleMode: ScaleMode.FIT, * manifest: { bundles: [ * { name: 'preload', assets: [{ alias: 'logo', src: 'logo.png' }] }, * { name: 'game', assets: [{ alias: 'bg', src: 'background.png' }] }, * ]}, * loading: { tapToStart: true }, * }); * * game.scenes.register('game', GameScene); * await game.start('game'); * ``` */ declare class GameApplication extends EventEmitter { /** PixiJS Application instance */ app: Application; /** Scene manager */ scenes: SceneManager; /** Asset manager */ assets: AssetManager; /** Audio manager */ audio: AudioManager; /** Input manager */ input: InputManager; /** Viewport manager */ viewport: ViewportManager; /** Scaled world root (holds scenes). Transformed by the ViewportManager to fit the design * resolution; lives below the UI layer on app.stage. */ worldRoot: Container; /** Unscaled, screen-space UI layer. Sits above {@link worldRoot} and is NOT touched by the * viewport transform — children fill the real screen (e.g. the host's shell + overlay). */ uiLayer: Container; /** SDK instance (null in offline mode) */ sdk: CasinoGameSDK | null; /** FPS overlay instance (only when debug: true) */ fpsOverlay: FPSOverlay | null; /** Data received from SDK initialization */ initData: InitData | null; /** Platform session (SDK + optional DevBridge). null until start() runs. */ platformSession: PlatformSession | null; /** Branded game shell (only when config.shell is set). */ shell?: _energy8platform_shell_html.GameShell; /** Configuration */ readonly config: GameApplicationConfig; private _running; private _destroyed; private _container; constructor(config?: GameApplicationConfig); /** Current game config from SDK (or null in offline mode) */ get gameConfig(): GameConfigData | null; /** Current session data */ get session(): SessionData | null; /** Current balance */ get balance(): number; /** Current currency */ get currency(): string; /** Whether the engine is running */ get isRunning(): boolean; /** * Start the game engine. This is the main entry point. * * @param firstScene - Key of the first scene to show after loading (must be registered) * @param sceneData - Optional data to pass to the first scene's onEnter */ start(firstScene: string, sceneData?: unknown): Promise; /** * Destroy the engine and free all resources. */ destroy(): Promise; private resolveContainer; private initPixi; private initSDK; private applySDKConfig; private initSubSystems; private loadAssets; } /** * Base class for all scenes. * Provides a root PixiJS Container and lifecycle hooks. * * @example * ```ts * class MenuScene extends Scene { * async onEnter() { * const bg = Sprite.from('menu-bg'); * this.container.addChild(bg); * } * * onUpdate(dt: number) { * // per-frame logic * } * * onResize(width: number, height: number) { * // reposition UI * } * } * ``` */ declare abstract class Scene implements IScene { readonly container: Container; constructor(); /** Called when this scene becomes active. Override in subclass. */ onEnter?(data?: unknown): Promise | void; /** Called when this scene is deactivated. Override in subclass. */ onExit?(): Promise | void; /** Called every frame with delta time (in seconds). Override in subclass. */ onUpdate?(dt: number): void; /** Called when the viewport resizes. Override in subclass. */ onResize?(width: number, height: number): void; /** Cleanup — called when the scene is permanently removed. */ onDestroy?(): void; } interface IntroSceneConfig { title?: string; logo?: string; tapToStart?: boolean; /** Where to navigate on tap. Defaults to the conventional 'game' key. */ next?: string; /** Optional explicit start callback. Takes precedence over `goto(next)`. */ onStart?: () => void; } /** * Reusable splash scene: shows a title (or logo) + "tap to start", then advances. * * The host no longer special-cases the intro. Navigation works like every other * scene: the host injects `goto(key)` into this scene's start data. On tap this * scene calls `onStart` if the game supplied one, otherwise `goto(next ?? 'game')`. * (The built-in can't know the game's scene key generically, so it falls back to * the conventional 'game' key — override via `next`. Scaffold-generated intros * skip this primitive and call `goto('game')` directly.) */ declare class IntroScene extends Scene { private layer?; onEnter(data?: unknown): Promise; onExit(): void; } export { EventEmitter, GameApplication, IntroScene, Scene, SceneManager }; export type { IntroSceneConfig };