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 };