import * as _utsp_types from '@utsp/types'; import { PostProcessConfig, ScalingMode, GridConfig, Vector2, ScalingModeValue, AxisSource, ButtonSource, InputBindingLoadPacket, AxisBinding, ButtonBinding, TouchZoneBinding, SoundInstanceId, AudioConfigCommand, PlaySoundCommand, StopSoundCommand, FadeOutSoundCommand, PauseSoundCommand, ResumeSoundCommand, SetSoundEffectsCommand, IAudioProcessor, VibrationPattern, GamepadVibrationOptions, GamepadVibrationCommand, IGamepadVibrationProcessor, MobileVibrationCommand, IMobileVibrationProcessor, AudioAck, PostProcessCommand, SoundFormat, SoundLoadType, SoundLoadPacket, SoundExternalLoadPacket, UserRenderState, RenderState } from '@utsp/types'; export { Vector2 } from '@utsp/types'; /** * Base interface for all network orders */ interface NetworkOrder { type: number; } /** * 0x01 - Char: Renders a single character at a specific position */ interface CharOrder extends NetworkOrder { type: 0x01; posX: number; posY: number; charCode: number; bgColorCode: number; fgColorCode: number; } /** * 0x02 - Text: Renders a string of characters with uniform colors */ interface TextOrder extends NetworkOrder { type: 0x02; posX: number; posY: number; text: string; bgColorCode: number; fgColorCode: number; } /** * 0x17 - TextMultiline: Renders multiple lines of text (\n for line breaks) */ interface TextMultilineOrder extends NetworkOrder { type: 0x17; posX: number; posY: number; text: string; bgColorCode: number; fgColorCode: number; } /** * 0x03 - SubFrame: Renders a rectangular region with uniform colors */ interface SubFrameOrder extends NetworkOrder { type: 0x03; posX: number; posY: number; sizeX: number; sizeY: number; bgColorCode: number; fgColorCode: number; frame: number[]; } /** * 0x04 - SubFrameMultiColor: Rectangular region with per-cell colors */ interface SubFrameMultiColorOrder extends NetworkOrder { type: 0x04; posX: number; posY: number; sizeX: number; sizeY: number; frame: Array<{ charCode: number; bgColorCode: number; fgColorCode: number; }>; } /** * 0x05 - FullFrame: Renders entire screen with uniform colors */ interface FullFrameOrder extends NetworkOrder { type: 0x05; bgColorCode: number; fgColorCode: number; frame: number[]; } /** * 0x06 - FullFrameMultiColor: Entire screen with per-cell colors */ interface FullFrameMultiColorOrder extends NetworkOrder { type: 0x06; frame: Array<{ charCode: number; bgColorCode: number; fgColorCode: number; }>; } /** * 0x07 - Sprite: Renders a preloaded unicolor sprite */ interface SpriteOrder extends NetworkOrder { type: 0x07; posX: number; posY: number; spriteIndex: number; bgColorCode: number; fgColorCode: number; } /** * 0x08 - SpriteMultiColor: Renders a preloaded multicolor sprite */ interface SpriteMultiColorOrder extends NetworkOrder { type: 0x08; posX: number; posY: number; spriteIndex: number; } /** * 0x09 - ColorMap: Applies colors to a region without changing characters */ interface ColorMapOrder extends NetworkOrder { type: 0x09; posX: number; posY: number; sizeX: number; sizeY: number; colorData: Array<{ bgColorCode: number; fgColorCode: number; }>; } /** * 0x0A - Shape: Renders geometric shapes */ interface ShapeOrder extends NetworkOrder { type: 0x0a; shapeType: ShapeType; shapeData: ShapeData; } declare enum ShapeType { Rectangle = 1, Circle = 2, Line = 3, Ellipse = 4, Triangle = 5 } type ShapeData = RectangleShape | CircleShape | LineShape | EllipseShape | TriangleShape; interface RectangleShape { posX: number; posY: number; width: number; height: number; filled: boolean; charCode: number; bgColorCode: number; fgColorCode: number; } interface CircleShape { centerX: number; centerY: number; radius: number; filled: boolean; charCode: number; bgColorCode: number; fgColorCode: number; } interface LineShape { x1: number; y1: number; x2: number; y2: number; charCode: number; bgColorCode: number; fgColorCode: number; } interface EllipseShape { centerX: number; centerY: number; radiusX: number; radiusY: number; filled: boolean; charCode: number; bgColorCode: number; fgColorCode: number; } interface TriangleShape { x1: number; y1: number; x2: number; y2: number; x3: number; y3: number; filled: boolean; charCode: number; bgColorCode: number; fgColorCode: number; } /** * 0x0B - DotCloud: Same character at multiple positions (up to 65535) */ interface DotCloudOrder extends NetworkOrder { type: 0x0b; charCode: number; bgColorCode: number; fgColorCode: number; positions: Array<{ posX: number; posY: number; }>; } /** * 0x0C - DotCloudMultiColor: Different characters at multiple positions */ interface DotCloudMultiColorOrder extends NetworkOrder { type: 0x0c; dots: Array<{ charCode: number; bgColorCode: number; fgColorCode: number; posX: number; posY: number; }>; } /** * 0x0D - SpriteCloud: Same unicolor sprite at multiple positions */ interface SpriteCloudOrder extends NetworkOrder { type: 0x0d; spriteIndex: number; bgColorCode: number; fgColorCode: number; positions: Array<{ posX: number; posY: number; }>; } /** * 0x0E - SpriteCloudMultiColor: Same multicolor sprite at multiple positions */ interface SpriteCloudMultiColorOrder extends NetworkOrder { type: 0x0e; spriteIndex: number; positions: Array<{ posX: number; posY: number; }>; } /** * 0x0F - SpriteCloudVaried: Different unicolor sprites at multiple positions */ interface SpriteCloudVariedOrder extends NetworkOrder { type: 0x0f; sprites: Array<{ spriteIndex: number; bgColorCode: number; fgColorCode: number; posX: number; posY: number; }>; } /** * 0x10 - SpriteCloudVariedMultiColor: Different multicolor sprites at multiple positions */ interface SpriteCloudVariedMultiColorOrder extends NetworkOrder { type: 0x10; sprites: Array<{ spriteIndex: number; posX: number; posY: number; }>; } /** * 0x11 - Bitmask: Renders a rectangular region with bitpacked presence mask * Each bit represents presence (1) or absence (0) of the character at that position * Ideal for: ore veins, destructible terrain, collision maps, fog of war, etc. */ interface BitmaskOrder extends NetworkOrder { type: 0x11; posX: number; posY: number; sizeX: number; sizeY: number; charCode: number; bgColorCode: number; fgColorCode: number; override: boolean; mask: Uint8Array; } /** * Cell variant definition for Bitmask4 order */ interface Bitmask4Variant { charCode: number; bgColorCode: number; fgColorCode: number; } /** * 0x12 - Bitmask4: Renders a rectangular region with 2-bit packed mask * Each 2-bit value represents: 0 = absence, 1-3 = variant index */ interface Bitmask4Order extends NetworkOrder { type: 0x12; posX: number; posY: number; sizeX: number; sizeY: number; override: boolean; variants: [Bitmask4Variant, Bitmask4Variant, Bitmask4Variant]; mask: Uint8Array; } /** * Cell variant definition for Bitmask16 order */ interface Bitmask16Variant { charCode: number; bgColorCode: number; fgColorCode: number; } /** * 0x18 - Bitmask16: Renders a rectangular region with 4-bit packed mask * Each 4-bit value represents: 0 = absence, 1-15 = variant index */ interface Bitmask16Order extends NetworkOrder { type: 0x18; posX: number; posY: number; sizeX: number; sizeY: number; override: boolean; variants: Bitmask16Variant[]; mask: Uint8Array; } /** * 0x19 - Polyline: Renders a line connecting multiple points * Uses Bresenham algorithm to draw between consecutive points. * Binary format: [type(1), charCode(1), fgColor(1), bgColor(1), pointCount(1), x1, y1, x2, y2, ...] */ interface PolylineOrder extends NetworkOrder { type: 0x19; charCode: number; fgColorCode: number; bgColorCode: number; points: Array<{ x: number; y: number; }>; } /** * 0x13 - Fill: Fills entire layer with single character and colors */ interface FillOrder extends NetworkOrder { type: 0x13; charCode: number; bgColorCode: number; fgColorCode: number; } /** * 0x14 - FillChar: Fills layer with repeating character pattern */ interface FillCharOrder extends NetworkOrder { type: 0x14; patternWidth: number; patternHeight: number; bgColorCode: number; fgColorCode: number; pattern: number[]; } /** * 0x15 - FillSprite: Fills layer with repeating unicolor sprite */ interface FillSpriteOrder extends NetworkOrder { type: 0x15; spriteIndex: number; bgColorCode: number; fgColorCode: number; } /** * 0x16 - FillSpriteMultiColor: Fills layer with repeating multicolor sprite */ interface FillSpriteMultiColorOrder extends NetworkOrder { type: 0x16; spriteIndex: number; } type AnyNetworkOrder = CharOrder | TextOrder | TextMultilineOrder | SubFrameOrder | SubFrameMultiColorOrder | FullFrameOrder | FullFrameMultiColorOrder | SpriteOrder | SpriteMultiColorOrder | ColorMapOrder | ShapeOrder | DotCloudOrder | DotCloudMultiColorOrder | BitmaskOrder | Bitmask4Order | Bitmask16Order | PolylineOrder | SpriteCloudOrder | SpriteCloudMultiColorOrder | SpriteCloudVariedOrder | SpriteCloudVariedMultiColorOrder | FillOrder | FillCharOrder | FillSpriteOrder | FillSpriteMultiColorOrder; /** * Encoder for UTSP network orders * Converts order interfaces to binary buffers following the UTSP protocol specification */ declare class OrderEncoder { /** * Main entry point - encodes any order type to binary buffer * @param order The order to encode * @param is16bit Whether to encode charCodes as 16-bit (2 bytes) instead of 8-bit (1 byte) */ encode(order: AnyNetworkOrder, is16bit?: boolean): Uint8Array; /** * Helper to write a charCode as 1 or 2 bytes depending on mode */ private writeCharCode; /** * Get the byte size for a charCode based on mode * Uses shared utility from constants */ private charCodeSize; private encodeCharOrder; private encodeTextOrder; private encodeTextMultilineOrder; private encodeSubFrameOrder; private encodeSubFrameMultiColorOrder; private encodeFullFrameOrder; private encodeFullFrameMultiColorOrder; private encodeSpriteOrder; private encodeSpriteMultiColorOrder; private encodeColorMapOrder; private encodeShapeOrder; private encodeShapeData; private encodeDotCloudOrder; private encodeDotCloudMultiColorOrder; private encodeBitmaskOrder; private encodeBitmask4Order; private encodeBitmask16Order; private encodePolylineOrder; private encodeSpriteCloudOrder; private encodeSpriteCloudMultiColorOrder; private encodeSpriteCloudVariedOrder; private encodeSpriteCloudVariedMultiColorOrder; private encodeFillOrder; private encodeFillCharOrder; private encodeFillSpriteOrder; private encodeFillSpriteMultiColorOrder; } /** * Internal command sink used by `Display` to enqueue network commands via `User`. * @internal */ interface DisplayCommandSink { setPostProcess(displayId: number, config: PostProcessConfig | null): void; setScanlinesEnabled(displayId: number, enabled: boolean): void; setScanlinesOpacity(displayId: number, opacity: number): void; setScanlinesPattern(displayId: number, pattern: 'horizontal' | 'vertical' | 'grid'): void; setAmbientEffect(displayId: number, config: boolean | { blur?: number; scale?: number; }): void; setAmbientEffectEnabled(displayId: number, enabled: boolean): void; setAmbientEffectBlur(displayId: number, blur: number): void; setAmbientEffectScale(displayId: number, scale: number): void; isAmbientEffectEnabled(displayId: number): boolean; getAmbientEffectConfig(displayId: number): { enabled: boolean; blur: number; scale: number; } | null; getPostProcessConfig(displayId: number): PostProcessConfig | null; setScalingMode(displayId: number, mode: ScalingMode): void; getScalingMode(displayId: number): ScalingMode | null; setCellSize(displayId: number, width: number, height: number): void; getCellSize(displayId: number): { cellWidth: number; cellHeight: number; }; setGrid(displayId: number, config: boolean | GridConfig): void; setGridEnabled(displayId: number, enabled: boolean): void; isGridEnabled(displayId: number): boolean; getGridConfig(displayId: number): GridConfig | null; switchPalette(displayId: number, slotId: number): void; getCurrentPaletteSlotId(displayId: number): number | null; } /** * Render pass definition for multi-pass rendering. */ interface RenderPassConfig { id: number; zMin: number; zMax: number; enabled?: boolean; } /** * Represents a display (camera) in the virtual world. * * Architecture: * - `Display` is a camera looking into the world. * - Layers are managed at the `User` level. * - `Display` defines viewport origin/size and display-specific settings. */ declare class Display { private id; private origin; private size; private commandSink?; private previousOrigin; private previousSize; private renderPasses; /** * Creates a new display. * * @param id - Display ID (0-255). * @param sizeX - Width in cells (1-256). * @param sizeY - Height in cells (1-256). * * @example * const display = new Display(0, 80, 45); * * @throws Error if `id`, `sizeX`, or `sizeY` are out of bounds. */ constructor(id?: number, sizeX?: number, sizeY?: number); /** * Returns the display ID (0-255). * * @returns The display ID. */ getId(): number; /** * Injects the command sink (set by `User`). * @internal */ setCommandSink(sink: DisplayCommandSink): void; /** * Returns the display origin in world space. * * @returns The current origin. * * @example * const origin = display.getOrigin(); */ getOrigin(): Vector2; /** * Sets the display origin in world space. * * @param origin - New world position. * * @example * display.setOrigin(new Vector2(10, 5)); */ setOrigin(origin: Vector2): void; /** * Moves the display origin by a delta. * * @param deltaX - Delta X in world cells. * @param deltaY - Delta Y in world cells. * * @example * display.moveOrigin(1, 0); */ moveOrigin(deltaX: number, deltaY: number): void; /** * Returns `true` if the origin changed since the last tick. * * @returns Whether the origin has changed. * @internal */ hasOriginChanged(): boolean; /** * Returns `true` if the size changed since the last tick. * * @returns Whether the size has changed. * @internal */ hasSizeChanged(): boolean; /** * Returns `true` if origin or size changed since the last tick. * * @returns Whether origin or size has changed. * @internal */ hasChanged(): boolean; /** * Resets change tracking to the current state. * @internal */ resetChangeTracking(): void; /** * Returns the display size in cells. * * @returns The current size in cells. * * @example * const size = display.getSize(); */ getSize(): Vector2; /** * Sets the display size in cells. * * @param size - New size in cells (1-256). * * @example * display.setSize(new Vector2(80, 45)); * * @throws Error if width/height are out of bounds. */ setSize(size: Vector2): void; /** * Returns the injected command sink. * @throws Error if this display is not attached to a `User`. */ private getSink; /** * Sets the post-process configuration for this display. * * @param config - Post-process config or `null` to disable. * * @example * display.setPostProcess({ * scanlines: { enabled: true, opacity: 0.2, pattern: 'horizontal' }, * }); * * @throws Error if the display is not attached to a `User`. */ setPostProcess(config: PostProcessConfig | null): void; /** * Enables or disables scanlines for this display. * * @param enabled - Whether scanlines are enabled. * * @example * display.setScanlinesEnabled(true); */ setScanlinesEnabled(enabled: boolean): void; /** * Sets scanlines opacity for this display. * * @param opacity - Opacity in range [0, 1]. * * @example * display.setScanlinesOpacity(0.35); */ setScanlinesOpacity(opacity: number): void; /** * Sets the scanlines pattern for this display. * * @param pattern - Pattern type. * * @example * display.setScanlinesPattern('grid'); */ setScanlinesPattern(pattern: 'horizontal' | 'vertical' | 'grid'): void; /** * Enables or configures the ambient effect for this display. * * @param config - `true`/`false` or an object with `blur`/`scale`. * * @example * display.setAmbientEffect({ blur: 40, scale: 1.4 }); */ setAmbientEffect(config: boolean | { blur?: number; scale?: number; }): void; /** * Enables or disables the ambient effect for this display. * * @param enabled - Whether the effect is enabled. * * @example * display.setAmbientEffectEnabled(false); */ setAmbientEffectEnabled(enabled: boolean): void; /** * Sets ambient blur intensity for this display. * * @param blur - Blur amount in pixels. * * @example * display.setAmbientEffectBlur(60); */ setAmbientEffectBlur(blur: number): void; /** * Sets ambient effect scale for this display. * * @param scale - Scale factor (>= 1). * * @example * display.setAmbientEffectScale(1.5); */ setAmbientEffectScale(scale: number): void; /** * Returns whether the ambient effect is enabled. * * @returns `true` if enabled. */ isAmbientEffectEnabled(): boolean; /** * Returns the current ambient effect configuration, or `null` if disabled. * * @returns The ambient effect config or `null`. */ getAmbientEffectConfig(): { enabled: boolean; blur: number; scale: number; } | null; /** * Returns the current post-process configuration, or `null` if none. * * @returns The post-process config or `null`. */ getPostProcessConfig(): PostProcessConfig | null; /** * Sets the pixel-perfect scaling mode for this display. * * @param mode - Scaling mode. * * @example * display.setScalingMode('integer'); */ setScalingMode(mode: ScalingMode): void; /** * Returns the current scaling mode for this display, if set. * * @returns The scaling mode or `null`. */ getScalingMode(): ScalingMode | null; /** * Sets the cell size (in pixels) for this display. * * @param width - Cell width in pixels. * @param height - Cell height in pixels. * * @example * display.setCellSize(8, 16); */ setCellSize(width: number, height: number): void; /** * Returns the current cell size for this display. * * @returns The cell size in pixels. */ getCellSize(): { cellWidth: number; cellHeight: number; }; /** * Enables or configures the debug grid overlay. * * @param config - `true`/`false` or a `GridConfig` object. * * @example * display.setGrid({ enabled: true, color: '#00ff00', lineWidth: 2 }); */ setGrid(config: boolean | GridConfig): void; /** * Enables or disables the debug grid overlay. * * @param enabled - Whether the grid is enabled. */ setGridEnabled(enabled: boolean): void; /** * Returns whether the debug grid is enabled. * * @returns `true` if enabled. */ isGridEnabled(): boolean; /** * Returns the current grid configuration, or `null` if none. * * @returns The grid configuration or `null`. */ getGridConfig(): GridConfig | null; /** * Switches to a preloaded palette slot for this display. * * @param slotId - Palette slot ID (0-255). * * @example * display.switchPalette(2); */ switchPalette(slotId: number): void; /** * Returns the active palette slot ID for this display, or `null`. * * @returns The current palette slot ID or `null`. */ getCurrentPaletteSlotId(): number | null; /** * Returns the render passes configuration. * * @returns Array of passes or `undefined` for single pass. */ getRenderPasses(): RenderPassConfig[] | undefined; /** * Sets render passes configuration (0..4 passes). * * @param passes - Pass definitions or `undefined` to reset. * * @example * display.setRenderPasses([ * { id: 0, zMin: 0, zMax: 127 }, * { id: 1, zMin: 128, zMax: 255 }, * ]); * * @throws Error if more than 4 passes are provided. */ setRenderPasses(passes: RenderPassConfig[] | undefined): void; private normalizePass; /** * Returns a debug-friendly snapshot of this display (metadata only). * * @returns Debug info snapshot. */ getDebugInfo(): { id: number; origin: { x: number; y: number; }; size: { x: number; y: number; }; renderPasses?: RenderPassConfig[]; }; } /** * Network representation of a display * Layers are NO LONGER under displays - they are at User level */ interface NetworkDisplay { /** Display ID (0-255) */ id: number; /** Origin X position in virtual world (0-65535) */ originX: number; /** Origin Y position in virtual world (0-65535) */ originY: number; /** Display width in cells (1-256) */ sizeX: number; /** Display height in cells (1-256) */ sizeY: number; /** Optional render pass configuration (max 4 passes) */ renderPasses?: RenderPassConfig[]; } /** * Encoder for UTSP Network Displays * Encodes display origin and size metadata following the UTSP protocol specification * * Note: In the new protocol architecture, layers are no longer nested in displays. * Displays are now pure origins (camera positions) with size, and layers are managed at the user level. */ declare class DisplayEncoder { /** * Encodes a NetworkDisplay to binary buffer * * Structure (per the new protocol): * - DisplayId: 1 byte * - OriginX: 2 bytes (big-endian) * - OriginY: 2 bytes (big-endian) * - SizeX: 1 byte (1-256 encoded as 0-255) * - SizeY: 1 byte (1-256 encoded as 0-255) * - PassCount: 1 byte (0-4 render passes) * - RenderPasses (optional): 4 bytes each → Id(1) + zMin(1) + zMax(1) + flags(1) * * Total: 8 bytes + (4 * passCount) per display */ encode(display: NetworkDisplay): Uint8Array; /** * Calculates the size of an encoded display * 8 bytes + 4 * passCount (DisplayId + OriginX + OriginY + SizeX + SizeY + PassCount + RenderPasses) */ calculateSize(_display: NetworkDisplay): number; } interface NetworkLayer { id: number; updateFlags: number; zIndex: number; originX: number; originY: number; width: number; height: number; orderCount: number; orders: AnyNetworkOrder[]; /** Optional decoded byte size of this layer segment in the update packet */ byteSize?: number; /** * Macro layer flag * - false: standard layer * - true: macro layer (ephemeral, local effects) * Encoded in updateFlags bit 5 */ isMacroLayer: boolean; /** * CharCode mode for this layer * - false: 8-bit charCodes (0-255) * - true: 16-bit charCodes (0-65535) * Encoded in updateFlags bit 6 */ is16bit: boolean; } /** * Encoder for UTSP Network Layers * Encodes layer metadata and orders following the UTSP protocol specification */ declare class LayerEncoder { private orderEncoder; constructor(); /** * Encodes a NetworkLayer to binary buffer * * Structure (new protocol): * - LayerId: 2 bytes (big-endian) * - UpdateFlags: 1 byte (bit 6 = charCode mode: 0=8bit, 1=16bit) * - ZIndex: 1 byte * - OriginX: 2 bytes (big-endian) * - OriginY: 2 bytes (big-endian) * - Width: 2 bytes (big-endian) * - Height: 2 bytes (big-endian) * - OrderCount: 1 byte * - Orders: variable (encoded orders with 8 or 16-bit charCodes) */ encode(layer: NetworkLayer): Uint8Array; /** * Calculates the size of an encoded layer without actually encoding it */ calculateSize(layer: NetworkLayer): number; } /** * UTSP Audio Order Types Enumeration * * Audio orders are separate from render orders and have their own type space. * They are included in the UpdatePacket but processed by the AudioProcessor, * not the Rasterizer. * * This separation ensures: * - Clear distinction between visual and audio orders * - Independent type numbering (0x01 audio ≠ 0x01 render) * - Extensibility without conflicts * * @example * ```typescript * import { AudioOrderType } from '@utsp/core'; * * const order = { type: AudioOrderType.PlaySound, soundId: 0, volume: 200 }; * ``` */ declare enum AudioOrderType { /** * 0x01 - PlaySound: Play a sound with optional spatial position * * Parameters: soundId, instanceId, flags, volume?, pitch?, fadeIn?, posX?, posY? */ PlaySound = 1, /** * 0x02 - PlayGlobalSound: Play a non-positional (global) sound * * Parameters: soundId, instanceId, flags, volume?, pitch?, fadeIn? */ PlayGlobalSound = 2, /** * 0x03 - StopSound: Stop a sound immediately * * Parameters: targetType, target (instanceId, soundId, or 'all') */ StopSound = 3, /** * 0x04 - FadeOutSound: Fade out and stop a sound * * Parameters: targetType, target, duration */ FadeOutSound = 4, /** * 0x05 - PauseSound: Pause a playing sound * * Parameters: targetType, target */ PauseSound = 5, /** * 0x06 - ResumeSound: Resume a paused sound * * Parameters: targetType, target */ ResumeSound = 6, /** * 0x07 - SetListenerPosition: Set the listener position for spatial audio * * Parameters: x, y (16-bit each) */ SetListenerPosition = 7, /** * 0x08 - ConfigureSpatial: Configure spatial audio parameters * * Parameters: maxDistance, referenceDistance, rolloffFactor, panSpread */ ConfigureSpatial = 8, /** * 0x09 - SetSoundEffects: Set audio effects on a playing sound * * Parameters: instanceId, flags, lowpass?, highpass?, reverb? */ SetSoundEffects = 9 } /** * Target type for stop/pause/resume/fadeout commands */ declare enum AudioTargetType { /** Target a specific instance by ID */ InstanceId = 0, /** Target all instances of a sound by soundId */ SoundId = 1, /** Target all sounds */ All = 2 } /** * UTSP Audio Orders * * Binary-encoded audio commands included in UpdatePacket. * Processed by AudioProcessor on client, synchronized with render orders. * * Each order has a specific binary structure optimized for network transmission. */ /** * Base interface for all audio orders */ interface AudioOrder { /** Audio order type identifier */ type: AudioOrderType; } /** * 0x01 - PlaySound: Play a sound with optional spatial position * * Binary structure (variable size, 4-15 bytes): * - type: 1 byte (0x01) * - soundId: 1 byte (0-255) * - instanceId: 2 bytes (big-endian, unique ID for this playback) * - flags: 1 byte (PlaySoundFlags bitfield) * - volume?: 1 byte (0-255, maps to 0.0-1.0) - if HasVolume * - pitch?: 1 byte (0-255, maps to 0.25x-4.0x, 128=1.0x) - if HasPitch * - fadeIn?: 1 byte (0-255, duration in 1/10 seconds, 0=instant) - if HasFadeIn * - posX?: 2 bytes (big-endian, 0-65535) - if HasPosition * - posY?: 2 bytes (big-endian, 0-65535) - if HasPosition * - lowpass?: 1 byte (0-255, * 100 = Hz cutoff) - if HasLowpass * - highpass?: 1 byte (0-255, * 100 = Hz cutoff) - if HasHighpass * - reverb?: 1 byte (0-255, / 255 = wet mix 0.0-1.0) - if HasReverb */ interface PlaySoundOrder extends AudioOrder { type: AudioOrderType.PlaySound; /** Sound ID (0-255) */ soundId: number; /** Unique instance ID for this playback */ instanceId: number; /** Flags bitfield */ flags: number; /** Volume (0-255, maps to 0.0-1.0). Present if HasVolume flag set */ volume?: number; /** Pitch (0-255, 128=1.0x). Present if HasPitch flag set */ pitch?: number; /** Fade in duration in 1/10 seconds. Present if HasFadeIn flag set */ fadeIn?: number; /** X position (0-65535). Present if HasPosition flag set */ posX?: number; /** Y position (0-65535). Present if HasPosition flag set */ posY?: number; /** Low-pass filter cutoff (0-255, * 100 = Hz). Present if HasLowpass flag set */ lowpass?: number; /** High-pass filter cutoff (0-255, * 100 = Hz). Present if HasHighpass flag set */ highpass?: number; /** Reverb wet mix (0-255, / 255 = 0.0-1.0). Present if HasReverb flag set */ reverb?: number; } /** * 0x02 - PlayGlobalSound: Play a non-positional sound * * Binary structure (variable size, 4-10 bytes): * - type: 1 byte (0x02) * - soundId: 1 byte (0-255) * - instanceId: 2 bytes (big-endian) * - flags: 1 byte (PlaySoundFlags, but HasPosition is ignored) * - volume?: 1 byte - if HasVolume * - pitch?: 1 byte - if HasPitch * - fadeIn?: 1 byte - if HasFadeIn * - lowpass?: 1 byte - if HasLowpass * - highpass?: 1 byte - if HasHighpass * - reverb?: 1 byte - if HasReverb */ interface PlayGlobalSoundOrder extends AudioOrder { type: AudioOrderType.PlayGlobalSound; /** Sound ID (0-255) */ soundId: number; /** Unique instance ID for this playback */ instanceId: number; /** Flags bitfield (HasPosition ignored) */ flags: number; /** Volume (0-255). Present if HasVolume flag set */ volume?: number; /** Pitch (0-255, 128=1.0x). Present if HasPitch flag set */ pitch?: number; /** Fade in duration in 1/10 seconds. Present if HasFadeIn flag set */ fadeIn?: number; /** Low-pass filter cutoff (0-255, * 100 = Hz). Present if HasLowpass flag set */ lowpass?: number; /** High-pass filter cutoff (0-255, * 100 = Hz). Present if HasHighpass flag set */ highpass?: number; /** Reverb wet mix (0-255, / 255 = 0.0-1.0). Present if HasReverb flag set */ reverb?: number; } /** * 0x03 - StopSound: Stop a sound immediately * * Binary structure (3-4 bytes): * - type: 1 byte (0x03) * - targetType: 1 byte (AudioTargetType) * - target: 1-2 bytes (instanceId=2B, soundId=1B, all=0B) */ interface StopSoundOrder extends AudioOrder { type: AudioOrderType.StopSound; /** Type of target */ targetType: AudioTargetType; /** Target value (instanceId, soundId, or undefined for 'all') */ target?: number; } /** * 0x04 - FadeOutSound: Fade out and stop a sound * * Binary structure (4-5 bytes): * - type: 1 byte (0x04) * - targetType: 1 byte * - duration: 1 byte (1/10 seconds, 0-25.5s) * - target: 1-2 bytes */ interface FadeOutSoundOrder extends AudioOrder { type: AudioOrderType.FadeOutSound; /** Type of target */ targetType: AudioTargetType; /** Fade duration in 1/10 seconds (0-255 = 0-25.5 seconds) */ duration: number; /** Target value */ target?: number; } /** * 0x05 - PauseSound: Pause a playing sound * * Binary structure (2-4 bytes): * - type: 1 byte (0x05) * - targetType: 1 byte * - target: 0-2 bytes */ interface PauseSoundOrder extends AudioOrder { type: AudioOrderType.PauseSound; /** Type of target */ targetType: AudioTargetType; /** Target value */ target?: number; } /** * 0x06 - ResumeSound: Resume a paused sound * * Binary structure (2-4 bytes): * - type: 1 byte (0x06) * - targetType: 1 byte * - target: 0-2 bytes */ interface ResumeSoundOrder extends AudioOrder { type: AudioOrderType.ResumeSound; /** Type of target */ targetType: AudioTargetType; /** Target value */ target?: number; } /** * 0x07 - SetListenerPosition: Set listener position for spatial audio * * Binary structure (5 bytes): * - type: 1 byte (0x07) * - x: 2 bytes (big-endian, 0-65535) * - y: 2 bytes (big-endian, 0-65535) */ interface SetListenerPositionOrder extends AudioOrder { type: AudioOrderType.SetListenerPosition; /** X position (0-65535) */ x: number; /** Y position (0-65535) */ y: number; } /** * 0x08 - ConfigureSpatial: Configure spatial audio parameters * * Binary structure (5 bytes): * - type: 1 byte (0x08) * - maxDistance: 1 byte (0-255, scaled appropriately) * - referenceDistance: 1 byte (0-255) * - rolloffFactor: 1 byte (0-255, maps to 0.0-2.55) * - panSpread: 1 byte (0-255, maps to 0.0-1.0) */ interface ConfigureSpatialOrder extends AudioOrder { type: AudioOrderType.ConfigureSpatial; /** Maximum audible distance (encoded, 0-255) */ maxDistance: number; /** Reference distance for full volume (encoded, 0-255) */ referenceDistance: number; /** Rolloff factor (0-255, maps to 0.0-2.55) */ rolloffFactor: number; /** Pan spread factor (0-255, maps to 0.0-1.0) */ panSpread: number; } /** * 0x09 - SetSoundEffects: Update audio effects on a playing sound * * Binary structure (variable size, 4-7 bytes): * - type: 1 byte (0x09) * - instanceId: 2 bytes (big-endian) * - flags: 1 byte (SoundEffectsFlags bitfield) * - lowpass?: 1 byte (0-255, * 100 = Hz cutoff) - if HasLowpass * - highpass?: 1 byte (0-255, * 100 = Hz cutoff) - if HasHighpass * - reverb?: 1 byte (0-255, / 255 = wet mix 0.0-1.0) - if HasReverb */ interface SetSoundEffectsOrder extends AudioOrder { type: AudioOrderType.SetSoundEffects; /** Target instance ID */ instanceId: number; /** Flags bitfield */ flags: number; /** Low-pass filter cutoff (0-255, * 100 = Hz). Present if HasLowpass flag set */ lowpass?: number; /** High-pass filter cutoff (0-255, * 100 = Hz). Present if HasHighpass flag set */ highpass?: number; /** Reverb wet mix (0-255, / 255 = 0.0-1.0). Present if HasReverb flag set */ reverb?: number; } /** * Union of all audio order types */ type AnyAudioOrder = PlaySoundOrder | PlayGlobalSoundOrder | StopSoundOrder | FadeOutSoundOrder | PauseSoundOrder | ResumeSoundOrder | SetListenerPositionOrder | ConfigureSpatialOrder | SetSoundEffectsOrder; /** * UTSP Vibration Order Types Enumeration * * Vibration orders handle both mobile and gamepad vibration feedback. * They are included in the UpdatePacket and processed by vibration processors on client. * * This enables tactile feedback synchronized with game events. * * @example * ```typescript * import { VibrationOrderType } from '@utsp/core'; * * const order = { type: VibrationOrderType.MobileVibrate, pattern: [100, 50, 100] }; * ``` */ declare enum VibrationOrderType { /** * 0x01 - MobileVibrate: Trigger a vibration pattern on mobile device * * Parameters: patternLength, pattern[], intensity? */ MobileVibrate = 1, /** * 0x02 - MobileCancel: Stop any ongoing mobile vibration * * No parameters */ MobileCancel = 2, /** * 0x10 - GamepadVibrate: Trigger dual-motor vibration on gamepad * * Parameters: gamepadIndex, duration, strongMagnitude, weakMagnitude, startDelay? */ GamepadVibrate = 16, /** * 0x11 - GamepadCancel: Stop vibration on gamepad * * Parameters: gamepadIndex (0xFF = all) */ GamepadCancel = 17 } /** * UTSP Vibration Orders * * Binary-encoded vibration commands included in UpdatePacket. * Supports both mobile vibration (pattern-based) and gamepad vibration (dual-motor). * * Each order has a specific binary structure optimized for network transmission. */ /** * Base interface for all vibration orders */ interface VibrationOrder { /** Vibration order type identifier */ type: VibrationOrderType; } /** * 0x01 - MobileVibrate: Trigger a vibration pattern on mobile device * * Binary structure (variable size, 3-N bytes): * - type: 1 byte (0x01) * - flags: 1 byte (MobileVibrateFlags bitfield) * - patternLength: 1 byte (0-255, number of pattern entries) * - pattern: patternLength * 2 bytes (each entry is 0-65535 ms, big-endian) * - intensity?: 1 byte (0-255, maps to 0.0-1.0) - if HasIntensity * * Maximum pattern length: 255 entries (510 bytes) * Pattern represents alternating vibrate/pause durations in milliseconds */ interface MobileVibrateOrder extends VibrationOrder { type: VibrationOrderType.MobileVibrate; /** Flags bitfield */ flags: number; /** Vibration pattern (array of durations in ms) */ pattern: number[]; /** Intensity (0-255, maps to 0.0-1.0). Present if HasIntensity flag set */ intensity?: number; } /** * 0x02 - MobileCancel: Stop any ongoing mobile vibration * * Binary structure (1 byte): * - type: 1 byte (0x02) */ interface MobileCancelOrder extends VibrationOrder { type: VibrationOrderType.MobileCancel; } /** * 0x10 - GamepadVibrate: Trigger dual-motor vibration on gamepad * * Binary structure (6-8 bytes): * - type: 1 byte (0x10) * - flags: 1 byte (GamepadVibrateFlags bitfield) * - gamepadIndex: 1 byte (0-3, ignored if AllGamepads flag) * - duration: 2 bytes (0-65535 ms, big-endian) * - strongMagnitude: 1 byte (0-255, maps to 0.0-1.0) * - weakMagnitude: 1 byte (0-255, maps to 0.0-1.0) * - startDelay?: 2 bytes (0-65535 ms, big-endian) - if HasStartDelay */ interface GamepadVibrateOrder extends VibrationOrder { type: VibrationOrderType.GamepadVibrate; /** Flags bitfield */ flags: number; /** Gamepad index (0-3), or 0xFF for all */ gamepadIndex: number; /** Duration in milliseconds */ duration: number; /** Strong motor magnitude (0-255) */ strongMagnitude: number; /** Weak motor magnitude (0-255) */ weakMagnitude: number; /** Start delay in milliseconds (optional) */ startDelay?: number; } /** * 0x11 - GamepadCancel: Stop vibration on gamepad * * Binary structure (3 bytes): * - type: 1 byte (0x11) * - flags: 1 byte (AllGamepads flag) * - gamepadIndex: 1 byte (0-3, or 0xFF for all) */ interface GamepadCancelOrder extends VibrationOrder { type: VibrationOrderType.GamepadCancel; /** Flags bitfield */ flags: number; /** Gamepad index (0-3), or 0xFF for all */ gamepadIndex: number; } /** * Union type of all vibration orders */ type AnyVibrationOrder = MobileVibrateOrder | MobileCancelOrder | GamepadVibrateOrder | GamepadCancelOrder; /** * UTSP Macro System Types * * Macros provide client-side feedback without server round-trips. * Used for: UI interactions (hover, focus), particle effects, visual effects. * * NOT for: synchronized gameplay, business logic, shared state. */ /** * Macro order types for UpdatePacket * These are processed separately from render orders (like AudioOrderType) */ declare enum MacroOrderType { /** * 0x01 - CreateInstance: Create a new macro instance */ CreateInstance = 1, /** * 0x02 - UpdateInstance: Update an existing instance's params */ UpdateInstance = 2, /** * 0x03 - RemoveInstance: Remove an instance */ RemoveInstance = 3 } /** * Macro event types sent from client to server * UI interactions that need server-side handling */ declare enum MacroEventType { /** * 0x01 - Click: Button/element was clicked */ Click = 1, /** * 0x02 - Change: Value changed (slider, checkbox, etc.) */ Change = 2, /** * 0x03 - Submit: Text/form was submitted */ Submit = 3, /** * 0x04 - Select: Item was selected (dropdown, list, etc.) */ Select = 4 } /** * Macro types - determines behavior and rendering */ type MacroType = 'ui' | 'particle' | 'effect' | 'reveal' | 'line'; /** * Base interface for all macro templates (loaded via LoadMacro) */ interface MacroTemplateBase { /** Unique template identifier */ id: string; /** Macro type */ type: MacroType; /** Parameter definitions (name → type) */ params?: Record; /** Sound triggers */ sounds?: { hover?: string; active?: string; focus?: string; }; } /** * Render command for UI macros * Coordinates are relative to instance position */ type RenderCommand = { fillRect: [ number | string, number | string, number | string, number | string, string, string, string ]; } | { text: [number | string, number | string, string, string]; } | { setCell: [number | string, number | string, string, string, string]; }; /** * UI subtypes for specialized behavior */ type UISubtype = 'generic' | 'button'; /** * Border style for button */ type ButtonBorderStyle = 'single' | 'double' | 'rounded' | 'none'; /** * Button-specific configuration */ interface ButtonConfig { /** Button label (can be dynamic $paramName) */ label: string; /** Button width (can be dynamic) */ width: number | string; /** Button height (can be dynamic, default: 3 for bordered, 1 for none) */ height?: number | string; /** Border style */ border?: ButtonBorderStyle; /** Sound ID to play on hover (optional) */ soundHover?: number; /** Sound ID to play on click (optional) */ soundClick?: number; /** Sound ID to play on release (optional) */ soundRelease?: number; } /** * Button state colors */ interface ButtonStateColors { /** Foreground color for border/frame */ fg?: number | string; /** Background color */ bg?: number | string; /** Text/label foreground color */ textFg?: number | string; /** Vertical offset (for "pressed" effect) */ offsetY?: number; } /** * UI macro template - interactive elements (button, slider, checkbox, etc.) */ interface UIMacroTemplate extends MacroTemplateBase { type: 'ui'; /** UI subtype for specialized behavior (default: 'generic') */ subtype?: UISubtype; /** Button-specific configuration (required when subtype: 'button') */ button?: ButtonConfig; /** Base render commands (executed in order) - used for generic subtype */ base?: RenderCommand[]; /** State-specific variable overrides */ states: { normal: Record | ButtonStateColors; hover?: Record | ButtonStateColors; active?: Record | ButtonStateColors; focused?: Record | ButtonStateColors; disabled?: Record | ButtonStateColors; }; } /** * Emitter configuration for particle macros */ interface ParticleEmitter { /** Spawn zone - where particles are created (relative coordinates) */ zone: { x: number | string; y: number | string; width: number | string; height: number | string; }; /** Viewport zone - only particles inside this area are rendered (relative coordinates) * If not specified, defaults to zone. Use this to let particles spawn outside * the visible area (e.g., rain spawning above screen) */ viewport?: { x: number | string; y: number | string; width: number | string; height: number | string; }; /** Particles per tick (continuous mode) */ rate?: number | string; /** Spawn N particles at once (burst mode) */ burst?: number | string; /** Only emit once then stop */ once?: boolean; /** Max simultaneous particles */ limit?: number; } /** * Particle configuration */ interface ParticleConfig { /** Character(s) to display - string, char code, or array */ char: string | number | (string | number)[]; /** Foreground color - palette index 0-255 */ fg: number | number[]; /** Background color - palette index 0-255 (optional, 255 = transparent) */ bg?: number | number[]; /** Lifetime in ticks (fixed or [min, max]) */ lifetime: number | [number, number]; /** Velocity (cells per tick) */ velocity: { x: number | string; y: number | string; } | { angle: number | [number, number]; speed: number | [number, number] | string; }; /** Fade out at end of lifetime */ fade?: boolean; } /** * Particle macro template - visual effects (rain, explosion, snow, etc.) */ interface ParticleMacroTemplate extends MacroTemplateBase { type: 'particle'; /** Emitter configuration */ emitter: ParticleEmitter; /** Particle configuration */ particle: ParticleConfig; } /** * Transform configuration for effect macros */ interface EffectTransform { /** Offset applied to rendering */ offset?: { x: number | string | { random: [number | string, number | string]; }; y: number | string | { random: [number | string, number | string]; }; }; /** Effect duration in ms */ duration: number | string; /** Decay over time */ decay?: boolean; } /** * Effect macro template - temporary visual effects (shake, flash, fade, etc.) */ interface EffectMacroTemplate extends MacroTemplateBase { type: 'effect'; /** Transform to apply */ transform: EffectTransform; } /** * Reveal patterns - how content is progressively shown/hidden */ type RevealPattern = 'typewriter' | 'typewriter-rev' | 'ltr' | 'rtl' | 'ttb' | 'btt' | 'random' | 'center-out' | 'spiral'; /** * Reveal direction - show or hide */ type RevealDirection = 'reveal' | 'hide'; /** * Cursor configuration for reveal macros */ interface RevealCursor { /** Cursor character (e.g., "▌", "_", "█") or char code */ char: string | number; /** Cursor foreground color (palette index) */ fg?: number; /** Cursor background color (palette index) */ bg?: number; /** Blink cursor */ blink?: boolean; /** Blink rate in ticks (default: 15) */ blinkRate?: number; } /** * Pause configuration for reveal macros */ interface RevealPause { /** Characters that trigger a pause (e.g., [".", "!", "?"]) */ chars: string[]; /** Pause duration in ticks */ duration: number; } /** * Single cell definition for 'cells' content type */ interface RevealCellDef { /** Character to display */ char: string | number; /** Foreground color (palette index) */ fg: number; /** Background color (palette index) */ bg: number; /** X position relative to instance */ x: number; /** Y position relative to instance */ y: number; } /** * Content to reveal - discriminated union with explicit type * All string values can be dynamic ($paramName references) */ type RevealContent = { type: 'text'; /** Text to reveal (multiline with \n) */ text: string; /** Foreground color (palette index, can be dynamic) */ fg?: number | string; /** Background color (palette index, can be dynamic) */ bg?: number | string; } | { type: 'sprite'; /** Sprite ID (0-255) or dynamic reference ($paramName) */ sprite: number | string; /** For unicolor sprites: foreground color (palette index, can be dynamic) */ fg?: number | string; /** For unicolor sprites: background color (palette index, can be dynamic) */ bg?: number | string; /** Use multicolor sprite instead of unicolor (default: true, tries multicolor first) */ multicolor?: boolean; } | { type: 'cells'; /** Array of cell definitions */ cells: RevealCellDef[]; } | { type: 'fill'; /** Character to fill with */ char: string | number; /** Foreground color (palette index, can be dynamic) */ fg: number | string; /** Background color (palette index, can be dynamic) */ bg: number | string; /** Width of fill area (can be dynamic) */ width: number | string; /** Height of fill area (can be dynamic) */ height: number | string; }; /** * Reveal macro template - progressive text/sprite display */ interface RevealMacroTemplate extends MacroTemplateBase { type: 'reveal'; /** Content to reveal (text or sprite) */ content: RevealContent; /** Reveal pattern */ pattern: RevealPattern; /** Direction: 'reveal' to show, 'hide' to hide */ direction: RevealDirection; /** Speed in cells per tick */ speed: number; /** Initial delay in ticks before starting */ delay?: number; /** Cursor configuration */ cursor?: RevealCursor; /** Pause on specific characters */ pauseOn?: RevealPause; } /** * Line macro template - dynamic line renderer with point history * The server sends only new points, the client maintains the full path */ interface LineMacroTemplate extends MacroTemplateBase { type: 'line'; /** Maximum number of points to keep in history (default: 100) */ maxPoints?: number; /** Character to draw the line with (default: '*') */ char: string | number; /** Foreground color (palette index) */ fg: number; /** Background color (palette index, default: 255 = transparent) */ bg?: number; /** Fade out older points (reduces alpha/color intensity) */ fadeOut?: boolean; /** Number of points to fade (from tail), default: all points if fadeOut is true */ fadeLength?: number; /** Close the path (connect last point to first) */ closed?: boolean; } /** * Any macro template type */ type MacroTemplate = UIMacroTemplate | ParticleMacroTemplate | EffectMacroTemplate | RevealMacroTemplate | LineMacroTemplate; /** * Base interface for macro orders */ interface MacroOrder { type: MacroOrderType; } /** * CreateInstance order - creates a new macro instance */ interface CreateInstanceOrder extends MacroOrder { type: MacroOrderType.CreateInstance; /** Instance ID (0-255) */ instanceId: number; /** Macro template ID (0-255) */ macroId: number; /** Layer ID (0-255) */ layerId: number; /** Position X (cells) */ x: number; /** Position Y (cells) */ y: number; /** Tab index for keyboard navigation (0 = not focusable) */ tabIndex: number; /** Instance parameters (JSON) */ params: Record; } /** * UpdateInstance order - updates an existing instance's params */ interface UpdateInstanceOrder extends MacroOrder { type: MacroOrderType.UpdateInstance; /** Instance ID (0-255) */ instanceId: number; /** Updated parameters (JSON) */ params: Record; } /** * RemoveInstance order - removes an instance */ interface RemoveInstanceOrder extends MacroOrder { type: MacroOrderType.RemoveInstance; /** Instance ID (0-255) */ instanceId: number; } /** * Any macro order type */ type AnyMacroOrder = CreateInstanceOrder | UpdateInstanceOrder | RemoveInstanceOrder; /** * Base interface for macro events */ interface MacroEvent { type: MacroEventType; /** Instance ID (0-255) */ instanceId: number; } /** * Click event - element was clicked */ interface ClickEvent extends MacroEvent { type: MacroEventType.Click; } /** * Change event - value changed */ interface ChangeEvent extends MacroEvent { type: MacroEventType.Change; /** New value (number or boolean) */ value: number | boolean; } /** * Submit event - text was submitted */ interface SubmitEvent extends MacroEvent { type: MacroEventType.Submit; /** Submitted text */ text: string; } /** * Select event - item was selected */ interface SelectEvent extends MacroEvent { type: MacroEventType.Select; /** Selected index */ index: number; } /** * Any macro event type */ type AnyMacroEvent = ClickEvent | ChangeEvent | SubmitEvent | SelectEvent; /** * UTSP Post-Process Order Types Enumeration * * Post-process orders control visual effects like scanlines and ambient effect. * They are included in the UpdatePacket and processed by the renderer. * * This separation ensures: * - Clear distinction between render, audio, and post-process orders * - Independent type numbering * - Perfect frame-level synchronization */ declare enum PostProcessOrderType { /** * 0x01 - SetConfig: Set full post-process configuration * * Parameters: flags, scanlines config?, ambient effect config? */ SetConfig = 1, /** * 0x02 - SetScanlines: Set scanlines configuration only * * Parameters: enabled, opacity?, pattern?, colorR?, colorG?, colorB? */ SetScanlines = 2, /** * 0x03 - SetAmbientEffect: Set ambient effect configuration only * * Parameters: enabled, blur?, scale?, opacity? */ SetAmbientEffect = 3, /** * 0x04 - SetScalingMode: Set pixel-perfect scaling mode * * Parameters: mode (ScalingMode enum value) */ SetScalingMode = 4, /** * 0x05 - SetGrid: Set debug grid overlay configuration * * Parameters: enabled, colorR?, colorG?, colorB?, colorA?, lineWidth? */ SetGrid = 5, /** * 0x06 - SwitchPalette: Switch to a pre-loaded palette slot * * Parameters: slotId (0-255) */ SwitchPalette = 6, /** * 0x07 - SetCellSize: Set cell dimensions in pixels * * Parameters: cellWidth (1-255), cellHeight (1-255) */ SetCellSize = 7 } /** * UTSP Post-Process Order Interfaces * * Defines the data structures for post-process orders sent over the network. * These orders control visual effects like scanlines and ambient effect. */ /** * Scanlines pattern type (2 bits) */ declare enum ScanlinesPatternType { Horizontal = 0, Vertical = 1, Grid = 2 } /** * Base interface for all post-process orders * * All post-process orders are scoped to a specific display. * The displayId identifies which display the order applies to. */ interface PostProcessOrderBase { /** Order type identifier */ type: PostProcessOrderType; /** Display ID this order applies to (0-255) */ displayId: number; } /** * SetConfig Order (0x01) * * Sets the full post-process configuration for a specific display. * Flags indicate which optional sections are present. * * Binary format: * - type: 1 byte (0x01) * - displayId: 1 byte (0-255) * - flags: 1 byte (PostProcessConfigFlags) * - [if HasScanlines]: * - enabled: 1 byte (0 or 1) * - opacity: 1 byte (0-255, scaled from 0.0-1.0) * - pattern: 1 byte (ScanlinesPatternType) * - colorR: 1 byte (0-255) * - colorG: 1 byte (0-255) * - colorB: 1 byte (0-255) * - [if HasAmbientEffect]: * - enabled: 1 byte (0 or 1) * - blur: 1 byte (0-255 pixels) * - scale: 1 byte (100-255, scaled from 1.0-2.55) * - opacity: 1 byte (0-255, scaled from 0.0-1.0) */ interface SetConfigOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetConfig; flags: number; /** Scanlines config (if HasScanlines flag set) */ scanlines?: { enabled: boolean; opacity: number; pattern: ScanlinesPatternType; colorR: number; colorG: number; colorB: number; }; /** Ambient effect config (if HasAmbientEffect flag set) */ ambientEffect?: { enabled: boolean; blur: number; scale: number; opacity: number; }; } /** * SetScanlines Order (0x02) * * Sets only the scanlines configuration for a specific display. * * Binary format: * - type: 1 byte (0x02) * - displayId: 1 byte (0-255) * - enabled: 1 byte (0 or 1) * - opacity: 1 byte (0-255, scaled from 0.0-1.0) * - pattern: 1 byte (ScanlinesPatternType) * - colorR: 1 byte (0-255) * - colorG: 1 byte (0-255) * - colorB: 1 byte (0-255) */ interface SetScanlinesOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetScanlines; enabled: boolean; opacity: number; pattern: ScanlinesPatternType; colorR: number; colorG: number; colorB: number; } /** * SetAmbientEffect Order (0x03) * * Sets only the ambient effect configuration for a specific display. * * Binary format: * - type: 1 byte (0x03) * - displayId: 1 byte (0-255) * - enabled: 1 byte (0 or 1) * - blur: 1 byte (0-255 pixels) * - scale: 1 byte (100-255, scaled from 1.0-2.55) * - opacity: 1 byte (0-255, scaled from 0.0-1.0) */ interface SetAmbientEffectOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetAmbientEffect; enabled: boolean; blur: number; scale: number; opacity: number; } /** * SetScalingMode Order (0x04) * * Sets the pixel-perfect scaling mode for a specific display. * * Binary format: * - type: 1 byte (0x04) * - displayId: 1 byte (0-255) * - mode: 1 byte (ScalingModeValue) */ interface SetScalingModeOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetScalingMode; mode: ScalingModeValue; } /** * SetGrid Order (0x05) * * Sets the debug grid overlay configuration for a specific display. * * Binary format: * - type: 1 byte (0x05) * - displayId: 1 byte (0-255) * - enabled: 1 byte (0 or 1) * - colorR: 1 byte (0-255) * - colorG: 1 byte (0-255) * - colorB: 1 byte (0-255) * - colorA: 1 byte (0-255, alpha as 0-255) * - lineWidth: 1 byte (1-10 pixels) */ interface SetGridOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetGrid; enabled: boolean; colorR: number; colorG: number; colorB: number; colorA: number; lineWidth: number; } /** * SwitchPalette Order (0x06) * * Switches to a pre-loaded palette slot for a specific display. * The palette must have been loaded to the Core via loadPaletteToSlot() first. * * Binary format: * - type: 1 byte (0x06) * - displayId: 1 byte (0-255) * - slotId: 1 byte (0-255) */ interface SwitchPaletteOrder extends PostProcessOrderBase { type: PostProcessOrderType.SwitchPalette; slotId: number; } /** * SetCellSize Order (0x07) * * Sets the cell dimensions in pixels for a specific display. * Used to configure the renderer's native cell size. * * Binary format: * - type: 1 byte (0x07) * - displayId: 1 byte (0-255) * - cellWidth: 1 byte (1-255 pixels) * - cellHeight: 1 byte (1-255 pixels) */ interface SetCellSizeOrder extends PostProcessOrderBase { type: PostProcessOrderType.SetCellSize; cellWidth: number; cellHeight: number; } /** * Union type for all post-process orders */ type AnyPostProcessOrder = SetConfigOrder | SetScanlinesOrder | SetAmbientEffectOrder | SetScalingModeOrder | SetGridOrder | SwitchPaletteOrder | SetCellSizeOrder; /** * Update packet according to the new UTSP protocol * * Structure: * - Tick (8 bytes): Frame counter for synchronization * - Displays: Viewport definitions * - Layers: Render orders for visual output * - Audio Orders: Audio commands synchronized with the frame * - Vibration Orders: Vibration commands (mobile + gamepad) synchronized with the frame * - Macro Orders: Macro instance commands (create, update, remove) * - PostProcess Orders: Visual effects commands (scanlines, ambient effect) * * Audio, Vibration, and PostProcess orders are in the same packet as render orders * to ensure perfect frame-level synchronization between visuals, sound, vibration, and effects. */ interface UpdatePacket { /** Tick counter (8 bytes) */ tick: number; /** Number of displays (1 byte) */ displayCount: number; /** List of displays with their origins */ displays: NetworkDisplay[]; /** Number of layers (2 bytes) */ layerCount: number; /** List of layers (shared across all displays) */ layers: NetworkLayer[]; /** Number of audio orders (1 byte) */ audioOrderCount: number; /** List of audio orders (synchronized with this frame) */ audioOrders: AnyAudioOrder[]; /** Number of vibration orders (1 byte) */ vibrationOrderCount: number; /** List of vibration orders (mobile + gamepad, synchronized with this frame) */ vibrationOrders: AnyVibrationOrder[]; /** Number of macro orders (1 byte) */ macroOrderCount: number; /** List of macro orders (create, update, remove instances) */ macroOrders: AnyMacroOrder[]; /** Number of post-process orders (1 byte) */ postProcessOrderCount: number; /** List of post-process orders (scanlines, ambient effect) */ postProcessOrders: AnyPostProcessOrder[]; /** * Optional: precise byte sizes (on-wire) for each section in this decoded packet. * Intended for debugging/telemetry (client-side). Not required for protocol correctness. */ __byteSizes?: { displays: number; layers: number; audioOrders: number; vibrationOrders: number; macroOrders: number; postProcessOrders: number; }; } /** * Encoder for UTSP Update Packets (Version 5) * Layers are now at User level, not under displays * * Structure: * - Tick: 8 bytes (big-endian) * - DisplayCount: 1 byte (8-bit unsigned integer, max 255 displays) * - For each Display: * - DisplayId: 1 byte * - OriginX: 2 bytes (big-endian) * - OriginY: 2 bytes (big-endian) * - SizeX: 1 byte (1-256, encoded as 0-255) * - SizeY: 1 byte (1-256, encoded as 0-255) * - PassCount: 1 byte (0-4 render passes) * - RenderPasses: 4 bytes each (Id + zMin + zMax + flags) * - LayerCount: 2 bytes (big-endian) * - For each Layer: (encoded by LayerEncoder) * - AudioOrderCount: 1 byte (max 255 audio orders per frame) * - For each AudioOrder: (encoded by AudioOrderEncoder) * - VibrationOrderCount: 1 byte (max 255 vibration orders per frame) * - For each VibrationOrder: (encoded by VibrationOrderEncoder) * - MacroOrderCount: 1 byte (max 255 macro orders per frame) * - For each MacroOrder: (encoded by MacroOrderEncoder) * - PostProcessOrderCount: 1 byte (max 255 post-process orders per frame) * - For each PostProcessOrder: (encoded by PostProcessOrderEncoder) */ declare class UpdatePacketEncoder { private layerEncoder; private audioOrderEncoder; private vibrationOrderEncoder; private macroOrderEncoder; private postProcessOrderEncoder; private displayEncoder; constructor(); /** * Encodes an UpdatePacket to binary buffer * * Minimum packet size: 15 bytes (Tick=8 + DisplayCount=1 + LayerCount=2 + AudioOrderCount=1 + VibrationOrderCount=1 + MacroOrderCount=1 + PostProcessOrderCount=1) */ encode(packet: UpdatePacket): Uint8Array; /** * Calculates the size of an encoded update packet without actually encoding it */ calculateSize(packet: UpdatePacket): number; /** * Creates an empty update packet (useful for keep-alive or no-op updates) */ static createEmptyPacket(tick: number | bigint): UpdatePacket; /** * Encodes an empty update packet (15 bytes minimum) */ encodeEmpty(tick: number | bigint): Uint8Array; } /** * UTSP Macro Order Encoder * * Encodes macro orders (CreateInstance, UpdateInstance, RemoveInstance) * for inclusion in UpdatePacket. * * Binary structures: * * CreateInstance (0x01): * - Type: 1 byte (0x01) * - InstanceId: 1 byte (0-255) * - MacroId: 1 byte (0-255) * - LayerId: 1 byte (0-255) * - X: 2 bytes (big-endian, signed int16) * - Y: 2 bytes (big-endian, signed int16) * - TabIndex: 1 byte (0-255, 0 = not focusable) * - ParamsLength: 2 bytes (big-endian) * - ParamsJson: N bytes (UTF-8 encoded JSON string) * * UpdateInstance (0x02): * - Type: 1 byte (0x02) * - InstanceId: 1 byte (0-255) * - ParamsLength: 2 bytes (big-endian) * - ParamsJson: N bytes (UTF-8 encoded JSON string) * * RemoveInstance (0x03): * - Type: 1 byte (0x03) * - InstanceId: 1 byte (0-255) */ /** * Encoder for macro orders in UpdatePacket */ declare class MacroOrderEncoder { /** * Main entry point - encodes any macro order to binary buffer */ encode(order: AnyMacroOrder): Uint8Array; /** * Calculate the size of an encoded macro order without encoding it */ calculateSize(order: AnyMacroOrder): number; private encodeCreateInstance; private calculateCreateInstanceSize; private encodeUpdateInstance; private calculateUpdateInstanceSize; private encodeRemoveInstance; } /** * Number of 256-char blocks in an atlas * - 1 block = 16×16 grid = 256 chars (8-bit) * - 4 blocks = 32×32 grid = 1024 chars (10-bit) * - 16 blocks = 64×64 grid = 4096 chars (12-bit) */ type AtlasBlocks = 1 | 4 | 16; /** * ImageFont configuration * For PNG atlas-based fonts with pre-rendered glyphs */ interface ImageFontConfig { glyphWidth: number; glyphHeight: number; cellWidth?: number; cellHeight?: number; atlasBlocks: AtlasBlocks; } /** * ImageFont class * Represents a PNG atlas-based font for extended character sets * Corresponds to LoadType 0x08 (ImageFont) in UTSP protocol * * Atlas layout for atlasBlocks=4 (1024 chars, 32×32 grid): * ``` * ┌─────────┬─────────┐ * │ 0-255 │ 256-511 │ * │ (Bloc 0)│ (Bloc 1)│ * ├─────────┼─────────┤ * │ 512-767 │768-1023 │ * │ (Bloc 2)│ (Bloc 3)│ * └─────────┴─────────┘ * ``` * * CharCode to UV mapping: * - col = charCode % atlasColumns * - row = floor(charCode / atlasColumns) * - u = col / atlasColumns * - v = row / atlasColumns */ declare class ImageFont { private fontId; private config; private readonly atlasColumns; private readonly maxCharCode; private blocks; constructor(fontId: number, config: ImageFontConfig); /** * Add image data for a specific block */ addBlock(blockIndex: number, data: Uint8Array): void; /** * Get image data for a specific block */ getBlock(blockIndex: number): Uint8Array | undefined; /** * Get the unique font ID */ getFontId(): number; /** * Get the full configuration */ getConfig(): ImageFontConfig; /** * Get the glyph width in pixels */ getGlyphWidth(): number; /** * Get the glyph height in pixels */ getGlyphHeight(): number; /** * Get the target cell width in pixels (rendering size) */ getCellWidth(): number; /** * Get the target cell height in pixels (rendering size) */ getCellHeight(): number; /** * Get the number of atlas blocks */ getAtlasBlocks(): AtlasBlocks; /** * Get the number of columns in the atlas grid */ getAtlasColumns(): number; /** * Get the maximum supported charCode */ getMaxCharCode(): number; /** * Get the atlas dimensions in pixels */ getAtlasDimensions(): { width: number; height: number; }; /** * Get UV coordinates for a character code * @param charCode Character code (0 to maxCharCode) * @returns UV coordinates { u1, v1, u2, v2 } or null if out of range */ getCharUV(charCode: number): { u1: number; v1: number; u2: number; v2: number; } | null; /** * Check if a charCode is valid for this atlas */ isValidCharCode(charCode: number): boolean; } /** * UTSP Macro Load Packet * * LoadType 0x07 - Macro template loading * JSON-based (not binary) since it's only loaded once at init time. */ /** * Macro Load (LoadType 0x07) * Loads a macro template for client-side instantiation */ interface MacroLoad { loadType: 0x07; /** Macro ID (0-255) - numeric ID for network transmission */ macroId: number; /** Macro template definition (JSON) */ template: MacroTemplate; } /** * UTSP Load Packet Types * Based on UTSP Protocol v0.1 - Section 4: Load Section */ /** * LoadType enumeration */ declare enum LoadType { ColorPalette = 1, Sprite = 2, MulticolorSprite = 3, Sound = 5, Macro = 7,// Macro template for client-side feedback ImageFont = 8,// Header for PNG atlas-based fonts (structure only) ImageFontBlock = 9 } /** * Color definition with RGBA+E values */ interface Color { colorId: number; r: number; g: number; b: number; a?: number; e?: number; } /** * Color Palette Load (LoadType 0x01) * Loads a palette of RGBA+E colors * * When slotId is provided, the palette is loaded into a named slot for later switching. * When slotId is undefined/absent, the palette is loaded into the default slot (0). * * @example * ```typescript * // Default slot (backward compatible) * { loadType: LoadType.ColorPalette, colors: [...] } // → treated as slot 0 * * // Palette slot for dynamic switching * { loadType: LoadType.ColorPalette, slotId: 1, colors: [...] } * ``` */ interface ColorPaletteLoad { loadType: LoadType.ColorPalette; slotId?: number; colors: Color[]; } /** * Simple Sprite Load (LoadType 0x02) * Each cell contains only a character code */ interface SpriteLoad { loadType: LoadType.Sprite; sprites: Array<{ spriteId: number; sizeX: number; sizeY: number; data: number[]; }>; } /** * Cell in a multicolor sprite */ interface MulticolorCell { charCode: number; fgColorId: number; bgColorId: number; } /** * Multicolor Sprite Load (LoadType 0x03) * Each cell contains charcode + foreground color + background color */ interface MulticolorSpriteLoad { loadType: LoadType.MulticolorSprite; sprites: Array<{ spriteId: number; sizeX: number; sizeY: number; data: MulticolorCell[]; }>; } /** * Sound Load (LoadType 0x05) * MIDI sound data for audio playback */ interface SoundLoad { loadType: LoadType.Sound; sounds: Array<{ soundId: number; midiData: Uint8Array; }>; } /** * ImageFont Load (LoadType 0x08) * PNG atlas-based fonts for extended character sets (256, 1024, or 4096 chars) * Note: Protocol supports only ONE active ImageFont. */ interface ImageFontLoad { loadType: LoadType.ImageFont; glyphWidth: number; glyphHeight: number; cellWidth: number; cellHeight: number; atlasBlocks: AtlasBlocks; } /** * ImageFont Block Load (LoadType 0x09) * Contains one block of PNG data for the active ImageFont. * One block = 256 characters (16x16 grid). */ interface ImageFontBlockLoad { loadType: LoadType.ImageFontBlock; blockIndex: number; imageData: Uint8Array; } /** * Union type for all load types */ type AnyLoad = ColorPaletteLoad | SpriteLoad | MulticolorSpriteLoad | SoundLoad | MacroLoad | ImageFontLoad | ImageFontBlockLoad; /** * Encoder for UTSP Load packets * Converts load operations to binary buffers following the UTSP protocol specification */ declare class LoadEncoder { /** * Main entry point - encodes any load type to binary buffer */ encode(load: AnyLoad): Uint8Array; /** * Encode ColorPaletteLoad (0x01) * Structure (default slot): LoadType(1) + HasSlot(1=0x00) + PaletteSize(1) + [ColorId(1) + R(1) + G(1) + B(1) + A(1) + E(1)]*N * Structure (slot palette): LoadType(1) + HasSlot(1=0xFF) + SlotId(1) + PaletteSize(1) + [ColorId(1) + R(1) + G(1) + B(1) + A(1) + E(1)]*N */ private encodeColorPalette; /** * Encode SpriteLoad (0x02) * Structure: LoadType(1) + SpriteCount(1) + [SpriteId(1) + SizeX(1) + SizeY(1) + Data(SizeX*SizeY)]*N */ private encodeSprite; /** * Encode MulticolorSpriteLoad (0x03) * Structure: LoadType(1) + SpriteCount(1) + [SpriteId(1) + SizeX(1) + SizeY(1) + Data(SizeX*SizeY*3)]*N */ private encodeMulticolorSprite; /** * Encode SoundLoad (0x05) * Structure: LoadType(1) + SoundCount(1) + [SoundId(1) + DataSize(2) + MidiData(DataSize)]*N */ private encodeSound; /** * Encode ImageFontLoad (0x08) * Header for PNG atlas-based fonts (structure only) * Structure: LoadType(1) + GlyphWidth(1) + GlyphHeight(1) + CellWidth(1) + CellHeight(1) + AtlasBlocks(1) */ private encodeImageFont; /** * Encode ImageFontBlockLoad (0x09) * Data block for PNG atlas-based fonts * Structure: LoadType(1) + BlockIndex(1) + DataLength(4) + Data */ private encodeImageFontBlock; } /** * UTSP Macro Load Encoder * * Encodes MacroLoad packets for network transmission. * Uses JSON for the template (simple, flexible, only sent once at init). * * Structure: * - LoadType: 1 byte (0x07) * - MacroId: 1 byte (0-255) * - JsonLength: 2 bytes (big-endian) * - JsonData: N bytes (UTF-8 encoded JSON string) */ /** * Encoder for MacroLoad packets */ declare class MacroLoadEncoder { /** * Encodes a MacroLoad to binary buffer */ encode(load: MacroLoad): Uint8Array; /** * Calculate the size of an encoded MacroLoad without encoding it */ calculateSize(load: MacroLoad): number; } /** * UTSP Macro Event Encoder * * Encodes macro events (Click, Change, Submit, Select) * for transmission from client to server. * * Binary structures: * * Click (0x01): * - Type: 1 byte (0x01) * - InstanceId: 1 byte (0-255) * * Change (0x02): * - Type: 1 byte (0x02) * - InstanceId: 1 byte (0-255) * - ValueType: 1 byte (0 = number, 1 = boolean) * - Value: 4 bytes (float32 for number) or 1 byte (0/1 for boolean) * * Submit (0x03): * - Type: 1 byte (0x03) * - InstanceId: 1 byte (0-255) * - TextLength: 2 bytes (big-endian) * - Text: N bytes (UTF-8 encoded string) * * Select (0x04): * - Type: 1 byte (0x04) * - InstanceId: 1 byte (0-255) * - Index: 2 bytes (big-endian, unsigned int16) */ /** * Encoder for macro events (client → server) */ declare class MacroEventEncoder { /** * Main entry point - encodes any macro event to binary buffer */ encode(event: AnyMacroEvent): Uint8Array; /** * Calculate the size of an encoded macro event without encoding it */ calculateSize(event: AnyMacroEvent): number; private encodeClick; private encodeChange; private calculateChangeSize; private encodeSubmit; private calculateSubmitSize; private encodeSelect; } /** * BufferCompat - Node.js AND browser compatible abstraction * * Replaces Buffer (Node.js only) with Uint8Array + DataView * which are standard APIs available everywhere. */ /** * Cross-platform compatible class for manipulating binary data * Uses Uint8Array (standard) instead of Buffer (Node.js only) */ declare class BufferCompat { private data; private view; constructor(size: number); /** * Creates a BufferCompat from an existing Uint8Array */ static from(array: Uint8Array): BufferCompat; /** * Allocates an uninitialized buffer (equivalent to Buffer.allocUnsafe) */ static allocUnsafe(size: number): BufferCompat; /** * Returns the buffer length */ get length(): number; /** * Returns the underlying Uint8Array */ toUint8Array(): Uint8Array; readUInt8(offset: number): number; readInt8(offset: number): number; readUInt16BE(offset: number): number; readInt16BE(offset: number): number; readUInt16LE(offset: number): number; readInt16LE(offset: number): number; readUInt32BE(offset: number): number; readInt32BE(offset: number): number; readUInt32LE(offset: number): number; readInt32LE(offset: number): number; readBigUInt64BE(offset: number): bigint; readBigInt64BE(offset: number): bigint; readBigUInt64LE(offset: number): bigint; readBigInt64LE(offset: number): bigint; writeUInt8(value: number, offset: number): void; writeInt8(value: number, offset: number): void; writeUInt16BE(value: number, offset: number): void; writeInt16BE(value: number, offset: number): void; writeUInt16LE(value: number, offset: number): void; writeInt16LE(value: number, offset: number): void; writeUInt32BE(value: number, offset: number): void; writeInt32BE(value: number, offset: number): void; writeUInt32LE(value: number, offset: number): void; writeInt32LE(value: number, offset: number): void; writeBigUInt64BE(value: bigint, offset: number): void; writeBigInt64BE(value: bigint, offset: number): void; writeBigUInt64LE(value: bigint, offset: number): void; writeBigInt64LE(value: bigint, offset: number): void; readFloatBE(offset: number): number; readFloatLE(offset: number): number; writeFloatBE(value: number, offset: number): void; writeFloatLE(value: number, offset: number): void; /** * Copies data from this buffer to another * @param target Destination buffer * @param targetStart Starting position in destination buffer * @param sourceStart Starting position in this buffer (optional) * @param sourceEnd Ending position in this buffer (optional) */ copy(target: BufferCompat, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; /** * Returns a portion of the buffer */ slice(start?: number, end?: number): BufferCompat; /** * Returns a view on a portion of the buffer (without copying) */ subarray(start?: number, end?: number): BufferCompat; /** * Converts to Node.js Buffer (if available) or returns the Uint8Array */ toBuffer(): Buffer | Uint8Array; /** * Creates a BufferCompat from a Node.js Buffer or Uint8Array */ static fromNodeBuffer(buffer: Buffer | Uint8Array): BufferCompat; } /** * Result of decoding an order from a buffer */ interface DecodeResult { order: T; bytesRead: number; } /** * Decoder for UTSP network orders * Converts binary buffers to order interfaces following the UTSP protocol specification */ declare class OrderDecoder { /** * Main entry point - decodes any order type from binary buffer * Returns the decoded order and the number of bytes consumed * @param buffer The buffer to decode from * @param offset The offset in the buffer to start decoding from * @param is16bit Whether charCodes are encoded as 16-bit (2 bytes) instead of 8-bit (1 byte) */ decode(buffer: BufferCompat, offset?: number, is16bit?: boolean): DecodeResult; /** * Helper to read a charCode as 1 or 2 bytes depending on mode * Returns [charCode, newOffset] */ private readCharCode; /** * Get the byte size for a charCode based on mode * Uses shared utility from constants */ private charCodeSize; private decodeCharOrder; private decodeTextOrder; private decodeTextMultilineOrder; private decodeSubFrameOrder; private decodeSubFrameMultiColorOrder; private decodeFullFrameOrder; private decodeFullFrameMultiColorOrder; private decodeSpriteOrder; private decodeSpriteMultiColorOrder; private decodeColorMapOrder; private decodeShapeOrder; private decodeShapeData; private decodeDotCloudOrder; private decodeDotCloudMultiColorOrder; private decodeBitmaskOrder; private decodeBitmask4Order; private decodeBitmask16Order; private decodePolylineOrder; private decodeSpriteCloudOrder; private decodeSpriteCloudMultiColorOrder; private decodeSpriteCloudVariedOrder; private decodeSpriteCloudVariedMultiColorOrder; private decodeFillOrder; private decodeFillCharOrder; private decodeFillSpriteOrder; private decodeFillSpriteMultiColorOrder; private checkSize; } /** * Result of decoding a display from a buffer */ interface DisplayDecodeResult { display: NetworkDisplay; bytesRead: number; } /** * Decoder for UTSP Network Displays * Decodes display origin metadata following the UTSP protocol specification (new architecture) * * Note: In the new protocol architecture, layers are no longer nested in displays. * Displays are now pure origins (camera positions), and layers are managed at the user level. */ declare class DisplayDecoder { /** * Decodes a NetworkDisplay from binary buffer * * Structure (per the new protocol): * - DisplayId: 1 byte * - OriginX: 2 bytes (big-endian) * - OriginY: 2 bytes (big-endian) * - SizeX: 1 byte (encoded as 0-255, actual value 1-256) * - SizeY: 1 byte (encoded as 0-255, actual value 1-256) * - PassCount: 1 byte (0-4 render passes) * - RenderPasses (optional): 4 bytes each → Id(1) + zMin(1) + zMax(1) + flags(1) * * Total: 8 bytes + (4 * passCount) per display */ decode(buffer: BufferCompat, offset?: number): DisplayDecodeResult; private checkSize; } /** * Result of decoding a layer from a buffer */ interface LayerDecodeResult { layer: NetworkLayer; bytesRead: number; } /** * Decoder for UTSP Network Layers * Decodes layer metadata and orders following the UTSP protocol specification */ declare class LayerDecoder { private orderDecoder; constructor(); /** * Decodes a NetworkLayer from binary buffer * * Structure: * - LayerId: 2 bytes (big-endian) * - UpdateFlags: 1 byte (bit 6 = charCode mode: 0=8bit, 1=16bit) * - ZIndex: 1 byte * - OriginX: 2 bytes (big-endian) * - OriginY: 2 bytes (big-endian) * - Width: 2 bytes (big-endian) * - Height: 2 bytes (big-endian) * - OrderCount: 1 byte * - Orders: variable (encoded orders with 8 or 16-bit charCodes) * * Header size: 13 bytes */ decode(buffer: BufferCompat, offset?: number): LayerDecodeResult; private checkSize; } /** * Decoder for UTSP Update Packets * Decodes complete update packets following the UTSP protocol specification (new architecture) */ declare class UpdatePacketDecoder { private displayDecoder; private layerDecoder; private audioOrderDecoder; private vibrationOrderDecoder; private macroOrderDecoder; private postProcessOrderDecoder; constructor(); /** * Decodes an UpdatePacket from binary buffer * * Structure (Version 5): * - Tick: 8 bytes (big-endian, 64-bit unsigned integer) * - DisplayCount: 1 byte (8-bit unsigned integer, max 255 displays) * - Displays: variable (DisplayId + OriginX + OriginY + SizeX + SizeY + PassCount + RenderPasses) * - LayerCount: 2 bytes (big-endian, 16-bit unsigned integer) * - Layers: variable (encoded layers) * - AudioOrderCount: 1 byte (max 255 audio orders) * - AudioOrders: variable (encoded audio orders) * - VibrationOrderCount: 1 byte (max 255 vibration orders) * - VibrationOrders: variable (encoded vibration orders - mobile + gamepad) * - MacroOrderCount: 1 byte (max 255 macro orders) * - MacroOrders: variable (encoded macro orders) * - PostProcessOrderCount: 1 byte (max 255 post-process orders) * - PostProcessOrders: variable (encoded post-process orders) * * Minimum packet size: 15 bytes (Tick + DisplayCount + LayerCount + AudioOrderCount + VibrationOrderCount + MacroOrderCount + PostProcessOrderCount) */ decode(data: Uint8Array, offset?: number): UpdatePacket; /** * Checks if a buffer contains a valid update packet header * Useful for validation before decoding */ isValid(data: Uint8Array, offset?: number): boolean; /** * Decodes only the tick and counts without decoding the displays/layers/audio/macros * Useful for quick inspection of packet metadata */ decodeHeader(data: Uint8Array, offset?: number): { tick: number; displayCount: number; layerCount: number; audioOrderCount?: number; macroOrderCount?: number; }; private checkSize; } /** * UTSP Macro Order Decoder * * Decodes macro orders from UpdatePacket. * * Binary structures: See MacroOrderEncoder.ts for format details. */ /** * Decoder for macro orders in UpdatePacket */ declare class MacroOrderDecoder { /** * Decodes a single macro order from buffer * @param buffer The buffer to decode from * @param offset Starting offset in the buffer * @returns Decoded order and the number of bytes consumed */ decode(buffer: BufferCompat, offset?: number): { order: AnyMacroOrder; bytesRead: number; }; /** * Decodes all macro orders from a buffer section * @param buffer The buffer to decode from * @param offset Starting offset * @param count Number of orders to decode * @returns Array of decoded orders and total bytes consumed */ decodeAll(buffer: BufferCompat, offset: number, count: number): { orders: AnyMacroOrder[]; bytesRead: number; }; private decodeCreateInstance; private decodeUpdateInstance; private decodeRemoveInstance; } /** * Result of decoding a load packet from a buffer */ interface LoadDecodeResult { load: AnyLoad; bytesRead: number; } /** * Decoder for UTSP Load packets * Converts binary buffers to load operation interfaces following the UTSP protocol specification */ declare class LoadDecoder { /** * Main entry point - decodes any load type from binary buffer */ decode(data: Uint8Array, offset?: number): LoadDecodeResult; /** * Decode ColorPaletteLoad (0x01) * Structure (default slot): LoadType(1) + HasSlot(1=0x00) + PaletteSize(1) + [ColorId(1) + R(1) + G(1) + B(1) + A(1) + E(1)]*N * Structure (slot palette): LoadType(1) + HasSlot(1=0xFF) + SlotId(1) + PaletteSize(1) + [ColorId(1) + R(1) + G(1) + B(1) + A(1) + E(1)]*N */ private decodeColorPalette; /** * Decode SpriteLoad (0x02) */ private decodeSprite; /** * Decode MulticolorSpriteLoad (0x03) */ private decodeMulticolorSprite; /** * Decode SoundLoad (0x05) */ private decodeSound; /** * Helper to check if buffer has enough bytes remaining */ private checkSize; /** * Decode MacroLoad (0x07) */ private decodeMacro; /** * Decode ImageFontLoad (0x08) * PNG atlas-based font for extended character sets */ private decodeImageFont; /** * Decode ImageFontBlockLoad (0x09) * Data block for PNG atlas-based fonts * Structure: LoadType(1) + BlockIndex(1) + DataLength(4) + Data */ private decodeImageFontBlock; } /** * UTSP Macro Load Decoder * * Decodes MacroLoad packets received from the server. * * Structure: * - LoadType: 1 byte (0x07) * - MacroId: 1 byte (0-255) * - JsonLength: 2 bytes (big-endian) * - JsonData: N bytes (UTF-8 encoded JSON string) */ /** * Decoder for MacroLoad packets */ declare class MacroLoadDecoder { /** * Decodes a MacroLoad from binary buffer * @param buffer The buffer to decode from * @param offset Starting offset in the buffer * @returns Decoded MacroLoad and the number of bytes consumed */ decode(buffer: BufferCompat, offset?: number): { load: MacroLoad; bytesRead: number; }; } /** * UTSP Macro Event Decoder * * Decodes macro events received from the client. * * Binary structures: See MacroEventEncoder.ts for format details. */ /** * Decoder for macro events (client → server) */ declare class MacroEventDecoder { /** * Decodes a single macro event from buffer * @param buffer The buffer to decode from * @param offset Starting offset in the buffer * @returns Decoded event and the number of bytes consumed */ decode(buffer: BufferCompat, offset?: number): { event: AnyMacroEvent; bytesRead: number; }; /** * Decodes all macro events from a buffer section * @param buffer The buffer to decode from * @param offset Starting offset * @param count Number of events to decode * @returns Array of decoded events and total bytes consumed */ decodeAll(buffer: BufferCompat, offset: number, count: number): { events: AnyMacroEvent[]; bytesRead: number; }; private decodeClick; private decodeChange; private decodeSubmit; private decodeSelect; } /** * CharCode mode for layers * - '8bit': 256 char codes (CP437 default) * - '16bit': 65536 char codes (256 atlas blocks × 256 chars) */ type CharCodeMode = '8bit' | '16bit'; interface Cell { charCode: number; fgColorCode: number; bgColorCode: number; } /** * Optimized buffer for cells * * CharCode storage depends on mode: * - 8-bit mode: 256 char codes * - 16-bit mode: 65536 char codes (atlas indices) * * Colors are always 8-bit (256 palette colors) * * Performance: * - clear(): ~5-10μs (vs 826μs with object array) */ declare class CellBuffer { private charCodes; private fgColors; private bgColors; private size; private is16bit; /** * Creates a new CellBuffer * @param size - Number of cells (default: 65536) * @param is16bit - true for 16-bit charCodes, false for 8-bit (default: false) */ constructor(size?: number, is16bit?: boolean); /** * Optimized clear: fills with "skip" values * - charCode = 0 (no character) * - fgColorCode = 255 (COLOR_SKIP = transparent) * - bgColorCode = 255 (COLOR_SKIP = transparent) * Performance: ~5-10μs (native TypedArray) */ clear(): void; /** * Optimized clear with uniform color * Faster than clear() when clearing to specific char/colors * Useful for background layers with uniform color * * Performance: ~2-5μs (loop unrolling friendly) * * @param charCode - Character to fill (e.g., 0x20 for space) * @param fgColorCode - Foreground color (0-255) * @param bgColorCode - Background color (0-255) * * @example * // Clear to black background * buffer.clearWithColor(0x20, 15, 0); */ clearWithColor(charCode: number, fgColorCode: number, bgColorCode: number): void; /** * Sets a cell at given index */ set(index: number, charCode: number, fgColorCode: number, bgColorCode: number): void; /** * Optimized batch fill for uniform char+colors (DotCloud, SpriteCloud) * Writes only characters at multiple positions with uniform fg/bg colors * * Performance: 2-3x faster than individual set() calls for uniform colors * * @param indices - Array of cell indices to fill * @param charCode - Character to write at all positions * @param fgColorCode - Uniform foreground color * @param bgColorCode - Uniform background color * * @example * // Rain drops (1,500 positions with same color) * const indices = raindrops.map(d => d.y * width + d.x); * buffer.fillCharsUniform(indices, 58, 20, 255); // ':' char, cyan, transparent */ fillCharsUniform(indices: number[], charCode: number, fgColorCode: number, bgColorCode: number): void; /** * Gets a cell at given index * Returns a Cell object for compatibility */ get(index: number): Cell; /** * Sets only character at given index (keeps existing colors) * Useful for operations that modify chars but preserve colors * * @param index - Cell index * @param charCode - Character code to write */ setCharOnly(index: number, charCode: number): void; /** * Sets only colors at given index (keeps existing char) * Useful for ColorMapOrder (updates colors, preserves chars) * * @param index - Cell index * @param fgColorCode - Foreground color * @param bgColorCode - Background color */ setColorsOnly(index: number, fgColorCode: number, bgColorCode: number): void; /** * Direct value access (faster than get()) * Note: These are called in hot loops, keep as inline as possible */ getCharCode(index: number): number; getFgColorCode(index: number): number; getBgColorCode(index: number): number; /** * Direct TypedArray access for charCodes (batch operations) */ getRawCharCodes(): Uint8Array | Uint16Array; /** * Direct TypedArray access for foreground colors */ getRawFgColors(): Uint8Array; /** * Direct TypedArray access for background colors */ getRawBgColors(): Uint8Array; /** * Buffer size (number of cells) */ getSize(): number; /** * Returns true if buffer uses 16-bit charCodes */ is16BitMode(): boolean; } /** * Optimized buffer to store only charcodes (unicolor sprites) * Simple structure: [char0, char1, char2, ...] * Always uses 16-bit charCodes to support atlas indices (0-65535) * * Performance: * - Less memory than CellBuffer (2 bytes vs 4 bytes per cell) * - Ideal for unicolor sprites where colors are defined at render time * - Cache-friendly with TypedArray */ declare class CharCodeBuffer { private data; private size; constructor(size: number); /** * Optimized clear: fills with zeros (no character) */ clear(): void; /** * Sets a charcode at given index */ set(index: number, charCode: number): void; /** * Gets a charcode at given index */ get(index: number): number; /** * Direct TypedArray access for batch operations */ getRawData(): Uint16Array; /** * Buffer size (number of cells) */ getSize(): number; } /** * Unicolor sprite - Stores only charcodes * Colors (fg/bg) are defined at render time via SpriteOrder * * Used with LoadType.Sprite (0x02) and SpriteOrder (0x07) */ interface UnicolorSprite { id: number; sizeX: number; sizeY: number; data: CharCodeBuffer; } /** * Multicolor sprite - Stores charcodes + fg/bg colors * Each cell contains its own color set * * Used with LoadType.MulticolorSprite (0x03) and SpriteMultiColorOrder (0x08) */ interface MulticolorSprite { id: number; sizeX: number; sizeY: number; data: CellBuffer; } /** * Public-facing types for Sprite Loading API * These types hide internal implementation details like LoadType * and support flexible input formats (strings, numbers, etc.) */ /** * Single Multicolor Cell definition */ interface MulticolorSpriteCell { /** * Character as a string (first char used) or numeric char code (0-65535). * Automatically converted to CP437 if a string is provided. */ charCode: string | number; /** * Foreground color ID (0-255) */ fgColorId: number; /** * Background color ID (0-255) */ bgColorId: number; } /** * Definition for a Unicolor Sprite */ interface UnicolorSpriteDefinition { /** * Unique sprite identifier (0-255) */ spriteId: number; /** * Width in cells (1-255) */ sizeX: number; /** * Height in cells (1-255) */ sizeY: number; /** * Sprite data. Can be: * - Flat array of numbers (char codes) * - Single string (will be treated as a sequence of characters) * - Array of strings (one per row) */ data: number[] | string | string[]; } /** * Definition for a Multicolor Sprite */ interface MulticolorSpriteDefinition { /** * Unique sprite identifier (0-255) */ spriteId: number; /** * Width in cells (1-255) */ sizeX: number; /** * Height in cells (1-255) */ sizeY: number; /** * Array of multicolor cells (row-major order, length = sizeX * sizeY) */ data: MulticolorSpriteCell[]; } /** * Central registry to manage unicolor and multicolor sprites * * Sprites are loaded via LoadPacket and referenced by their ID * in Orders (SpriteOrder, SpriteMultiColorOrder, etc.) * * Architecture: * - Two separate registries for unicolor and multicolor * - No possible collision between the two types * - Memory optimization with CharCodeBuffer for unicolor */ declare class SpriteRegistry { private unicolorSprites; private multicolorSprites; /** * Loads unicolor sprites from public definitions * Supports strings, string arrays, and numbers */ loadUnicolorSprites(definitions: UnicolorSpriteDefinition[] | SpriteLoad): void; /** * Internal method to load unicolor sprites from protocol packet * @internal */ loadUnicolorSpritesFromPacket(loadData: SpriteLoad): void; /** * Loads multicolor sprites from public definitions * Supports string or number charCodes */ loadMulticolorSprites(definitions: MulticolorSpriteDefinition[] | MulticolorSpriteLoad): void; /** * Internal method to load multicolor sprites from protocol packet * @internal */ loadMulticolorSpritesFromPacket(loadData: MulticolorSpriteLoad): void; /** * Retrieves a unicolor sprite by its ID */ getUnicolorSprite(id: number): UnicolorSprite | undefined; /** * Retrieves a multicolor sprite by its ID */ getMulticolorSprite(id: number): MulticolorSprite | undefined; /** * Checks if a unicolor sprite exists */ hasUnicolorSprite(id: number): boolean; /** * Checks if a multicolor sprite exists */ hasMulticolorSprite(id: number): boolean; /** * Removes a unicolor sprite */ unloadUnicolorSprite(id: number): boolean; /** * Removes a multicolor sprite */ unloadMulticolorSprite(id: number): boolean; /** * Clears all unicolor sprites */ clearUnicolorSprites(): void; /** * Clears all multicolor sprites */ clearMulticolorSprites(): void; /** * Clears all sprites (unicolor and multicolor) */ clearAll(): void; /** * Number of loaded unicolor sprites */ getUnicolorSpriteCount(): number; /** * Number of loaded multicolor sprites */ getMulticolorSpriteCount(): number; /** * Total number of loaded sprites */ getTotalSpriteCount(): number; } /** * LayerRasterizer * * Rasterizes drawing orders into a layer's cell buffer. * Always clears the buffer first, then rasterizes all orders. * The result is stored in Layer.data[] and reused during display composition. * * Responsibilities: * - Clear buffer before rasterization * - Translate each order into individual cells * - Write directly to the layer buffer * - Handle clipping (bounds checking) * * @example * ```typescript * const rasterizer = new LayerRasterizer(); * rasterizer.rasterizeOrders( * [ * { type: 0x13, charCode: 32, bgColorCode: 0, fgColorCode: 15 }, * { type: 0x02, posX: 20, posY: 5, text: "Hello", bgColorCode: 0, fgColorCode: 10 } * ], * layer.getData(), * 80, * 25 * ); * ``` */ declare class LayerRasterizer { private currentWidth; private currentHeight; /** * Rasterizes a set of orders into a layer buffer. * Always clears the buffer first, then rasterizes all orders. * * @param orders - Orders to rasterize * @param layerBuffer - Layer cell buffer (variable size up to 256×256) * @param layerWidth - Layer width (1-256) * @param layerHeight - Layer height (1-256) * @param spriteRegistry - Sprite registry for sprite-type orders (optional) */ rasterizeOrders(orders: any[], layerBuffer: CellBuffer, layerWidth: number, layerHeight: number, spriteRegistry?: SpriteRegistry): void; /** * Completely clears the buffer (all cells to 0) * Optimized with TypedArray.fill(): ~5-10μs instead of 826μs! */ clearBuffer(buffer: CellBuffer): void; /** * Rasterizes an individual order */ private rasterizeOrder; /** * 0x01 - Char: Single character at a position */ private rasterizeChar; /** * 0x02 - Text: Horizontal character string */ private rasterizeText; /** * 0x17 - TextMultiline: Multiple lines of text (\n for line breaks) */ private rasterizeTextMultiline; /** * 0x03 - SubFrame: Rectangular zone with uniform colors */ private rasterizeSubFrame; /** * 0x04 - SubFrameMultiColor: Rectangular zone with colors per cell */ private rasterizeSubFrameMultiColor; /** * 0x05 - FullFrame: Entire layer with uniform colors */ private rasterizeFullFrame; /** * 0x06 - FullFrameMultiColor: Entire layer with per-cell colors */ private rasterizeFullFrameMultiColor; /** * 0x09 - ColorMap: Applies colors to region without changing characters */ private rasterizeColorMap; /** * 0x0A - Shape: Geometric shapes (Rectangle, Circle, Line, etc.) */ private rasterizeShape; /** * Rectangle (filled or border) */ private rasterizeRectangle; /** * Circle (Bresenham algorithm) */ private rasterizeCircle; /** * Line (Bresenham algorithm) */ private rasterizeLine; /** * 0x19 - Polyline: Connected line segments through multiple points * Uses Bresenham algorithm to draw lines between consecutive points */ private rasterizePolyline; /** * Helper: Draw a line segment using Bresenham algorithm */ private rasterizeLineSegment; /** * Ellipse (algorithm similar to circle but with 2 radii) */ private rasterizeEllipse; /** * Triangle (scan-line rasterization) */ private rasterizeTriangle; /** * Draws a line segment (helper for triangle) */ private drawLineSegment; /** * 0x0B - DotCloud: Same character at multiple positions * OPTIMIZED: Uses set() for each position with uniform colors */ private rasterizeDotCloud; /** * 0x0C - DotCloudMultiColor: Different characters at multiple positions */ private rasterizeDotCloudMultiColor; /** * 0x11 - Bitmask: Bitpacked presence/absence mask with uniform character * Perfect for ore veins, collision maps, destructible terrain, fog of war * OPTIMIZED: Direct buffer access for batch processing * * Override modes: * - override = true: absences write transparent cells (255, 255, 255) * - override = false: absences preserve existing layer content */ private rasterizeBitmask; /** * 0x12 - Bitmask4: 2-bit packed mask with 3 visual variants * OPTIMIZED: Direct buffer access for batch processing * * Override modes: * - override = true: absences (value 0) write transparent cells (255, 255, 255) * - override = false: absences preserve existing layer content */ private rasterizeBitmask4; /** * 0x18 - Bitmask16: 4-bit packed mask with 15 visual variants * OPTIMIZED: Direct buffer access for batch processing * * Override modes: * - override = true: absences (value 0) write transparent cells (255, 255, 255) * - override = false: absences preserve existing layer content */ private rasterizeBitmask16; /** * 0x13 - Clear: Fills entire layer * OPTIMIZED: Uses clearWithColor() for fast uniform fill */ private rasterizeFill; /** * 0x14 - FillChar: Fills with a repeating pattern */ private rasterizeFillChar; /** * 0x07 - Sprite: Renders a unicolor sprite at a position */ private rasterizeSprite; /** * 0x08 - SpriteMultiColor: Renders a multicolor sprite at a position */ private rasterizeSpriteMultiColor; /** * 0x0D - SpriteCloud: Same unicolor sprite at multiple positions * OPTIMIZED: Uses batch fill for 1×1 sprites (common case for particles) */ private rasterizeSpriteCloud; /** * 0x0E - SpriteCloudMultiColor: Same multicolor sprite at multiple positions */ private rasterizeSpriteCloudMultiColor; /** * 0x0F - SpriteCloudVaried: Different unicolor sprites at multiple positions */ private rasterizeSpriteCloudVaried; /** * 0x10 - SpriteCloudVariedMultiColor: Different multicolor sprites at multiple positions */ private rasterizeSpriteCloudVariedMultiColor; /** * 0x15 - FillSprite: Tiles entire layer with unicolor sprite pattern */ private rasterizeFillSprite; /** * 0x16 - FillSpriteMultiColor: Tiles entire layer with multicolor sprite pattern */ private rasterizeFillSpriteMultiColor; /** * Sets a cell with automatic clipping */ private setCell; /** * Checks if a position is within layer bounds */ private isInBounds; } /** * ImageFontRegistry - Registry for PNG atlas-based fonts */ /** * Options for loading an ImageFont */ interface ImageFontOptions { /** Width of each glyph in pixels (8, 16, or 32) */ glyphWidth: number; /** Height of each glyph in pixels (8, 16, or 32) */ glyphHeight: number; /** Target cell width for rendering (default: glyphWidth) */ cellWidth?: number; /** Target cell height for rendering (default: glyphHeight) */ cellHeight?: number; /** Number of 256-char blocks: 1, 4, or 16 (default: 1) */ atlasBlocks?: AtlasBlocks; } /** * Registry for managing ImageFont instances * Each font is identified by a unique fontId (0-255) and a human-readable name * ImageFonts correspond to LoadType 0x08 (ImageFont) in UTSP protocol */ declare class ImageFontRegistry { private fonts; private nameToId; private nextId; /** * Allocate the next available font ID * @param name Font name (for error messages) * @returns Allocated font ID * @throws Error if no IDs available */ private allocateId; /** * Register a new ImageFont structure (no data) * Use addBlock() to add image data * @param name Human-readable name for the font * @param options Font options (glyph size, atlas blocks) * @returns The assigned font ID (0-255) * @throws Error if name already exists */ registerFont(name: string, options: ImageFontOptions): number; /** * Add a data block to an existing font * @param fontId The font ID * @param blockIndex Block index (0-15) * @param data PNG image data */ addBlock(fontId: number, blockIndex: number, data: Uint8Array): void; /** * Load a new ImageFont into the registry with specific ID (low-level API) * @param fontId Unique font identifier (0-255) * @param config ImageFont configuration * @throws Error if font ID already exists */ loadFont(fontId: number, config: ImageFontConfig): void; /** * Get an ImageFont by ID * @param fontId Font identifier * @returns ImageFont instance or undefined if not found */ getFont(fontId: number): ImageFont | undefined; /** * Get an ImageFont by name * @param name Font name * @returns ImageFont instance or undefined if not found */ getFontByName(name: string): ImageFont | undefined; /** * Get font ID by name * @param name Font name * @returns Font ID or undefined if not found */ getFontId(name: string): number | undefined; /** * Get font name by ID * @param fontId Font ID * @returns Font name or undefined if not found */ getFontName(fontId: number): string | undefined; /** * Check if a font exists in the registry by ID * @param fontId Font identifier * @returns true if font exists, false otherwise */ hasFont(fontId: number): boolean; /** * Check if a font exists in the registry by name * @param name Font name * @returns true if font exists, false otherwise */ hasFontByName(name: string): boolean; /** * Remove an ImageFont from the registry * @param fontId Font identifier * @returns true if font was removed, false if not found */ unloadFont(fontId: number): boolean; /** * Remove an ImageFont from the registry by name * @param name Font name * @returns true if font was removed, false if not found */ unloadFontByName(name: string): boolean; /** * Remove all fonts from the registry */ clearFonts(): void; /** * Get all font IDs in the registry * @returns Array of font IDs, sorted in ascending order */ getFontIds(): number[]; /** * Get all font names in the registry * @returns Array of font names */ getFontNames(): string[]; /** * Get all ImageFont instances in the registry * @returns Array of ImageFont instances, sorted by font ID */ getAllFonts(): ImageFont[]; /** * Get the number of fonts in the registry * @returns Number of fonts */ getFontCount(): number; } /** * MacroHandler - Base interface for macro type handlers * * Each handler manages a specific type of macro: * - UIHandler: Buttons, interactive elements * - ParticleHandler: Particle systems (rain, snow, explosions) * - EffectHandler: Screen effects (shake, flash) * - RevealHandler: Progressive text/sprite reveal */ /** * Base interface for macro instance */ interface BaseMacroInstance { instanceId: number; macroId: number; layerId: number; x: number; y: number; params: Record; } /** * UI macro instance with state tracking */ interface UIInstance extends BaseMacroInstance { type: 'ui'; tabIndex: number; isHovered: boolean; isFocused: boolean; isActive: boolean; isDisabled: boolean; } /** * Single particle in a particle system */ interface Particle { x: number; y: number; vx: number; vy: number; char: string; fg: number; bg?: number; lifetime: number; maxLifetime: number; } /** * Particle macro instance with active particles */ interface ParticleInstance extends BaseMacroInstance { type: 'particle'; particles: Particle[]; timeSinceEmit: number; emitDone: boolean; } /** * Effect macro instance with animation state */ interface EffectInstance extends BaseMacroInstance { type: 'effect'; elapsed: number; duration: number; currentOffsetX: number; currentOffsetY: number; } /** * Cell data for reveal macro */ interface RevealCell { char: string; fg: number; bg: number; x: number; y: number; revealOrder: number; } /** * Reveal macro instance with progressive display state */ interface RevealInstance extends BaseMacroInstance { type: 'reveal'; cells: RevealCell[]; revealedCount: number; totalCells: number; delayRemaining: number; pauseRemaining: number; finished: boolean; direction: 'reveal' | 'hide'; speed: number; blinkState: boolean; blinkTimer: number; } /** * Point in a line renderer */ interface LinePoint { x: number; y: number; addedAt: number; } /** * Line macro instance with point history */ interface LineInstance extends BaseMacroInstance { type: 'line'; points: LinePoint[]; currentTick: number; } /** * Union of all macro instance types */ type MacroInstance = UIInstance | ParticleInstance | EffectInstance | RevealInstance | LineInstance; /** * MacroEngine - Client-side macro execution engine * * Manages: * - Macro template storage * - Instance lifecycle * - UI state machine (hover, focus, active) * - Particle systems * - Effect animations * - Order generation for rendering * * This engine is part of @utsp/core and runs on the client side. * It receives macro orders from the server and generates render orders locally. */ /** * Result of macro engine update * Only contains events to send to server - sounds are played directly via callback */ interface MacroUpdateResult { /** Events to send to server (click, change, etc.) */ events: { instanceId: number; event: string; data?: unknown; }[]; } /** * MacroEngine - Executes macros on the client side */ declare class MacroEngine { private templates; private instances; private spriteRegistry; private playSound; private focusedInstanceId; private focusableInstances; private mouseX; private mouseY; private mouseDown; private readonly particleHandler; private readonly effectHandler; private readonly revealHandler; private readonly lineHandler; private instancesToDelete; /** * Injects SpriteRegistry (called by User when setting up) * Required for reveal macros with sprite content */ setSpriteRegistry(registry: SpriteRegistry): void; /** * Sets audio callback for playing sounds directly from macros * Sounds are played immediately on state changes (hover, click, etc.) * * @param callback - Function that plays a sound by ID */ setAudioCallback(callback: (soundId: number) => void): void; /** * Create the context object used by handlers */ private createContext; /** * Load a macro template (called when receiving LoadMacro packet) */ loadTemplate(macroId: number, template: MacroTemplate): void; /** * Get a template by ID */ getTemplate(macroId: number): MacroTemplate | undefined; /** * Check if a template is loaded */ hasTemplate(macroId: number): boolean; /** * Apply a macro order (called when decoding UpdatePacket) */ applyOrder(order: AnyMacroOrder): void; /** * Create a new instance */ private createInstance; /** * Update an existing instance */ private updateInstance; /** * Remove an instance */ private removeInstance; private addToFocusableList; private removeFromFocusableList; /** * Move focus to next focusable element */ focusNext(): void; /** * Move focus to previous focusable element */ focusPrevious(): void; /** * Set focus to a specific instance */ setFocus(instanceId: number | null): void; /** * Update mouse state for UI interaction * @param x - Mouse X position in cells (world coordinates) * @param y - Mouse Y position in cells (world coordinates) * @param isDown - Whether the mouse button is pressed */ updateMouse(x: number, y: number, isDown: boolean): void; /** * Estimate UI element size from template configuration */ private estimateUISize; /** * Activate the currently focused element (trigger click) */ activateFocused(): void; /** * Update all instances (called each tick) * * @returns Update result with events to send to server */ update(): MacroUpdateResult; /** * Get render orders grouped by layer */ getOrdersByLayer(): Map; /** * Render a single instance to orders */ private renderInstance; /** * Resolve a value that may be a parameter reference ($param) */ private resolveValue; /** * Resolve a random value (number or [min, max]) */ private resolveRandomValue; /** * Get the current effect offset (for screen shake, etc.) */ getEffectOffset(): { x: number; y: number; }; /** * Get all active instances */ getAllInstances(): MacroInstance[]; /** * Get instance count */ getInstanceCount(): number; /** * Clear all templates and instances */ clear(): void; } /** * MacroRegistry - Registry for macro templates and instances * * Manages: * - Macro templates (loaded via loadMacro) * - Macro instances (created via createInstance) * - Name → ID mappings for network transmission * * Used on both server (for encoding) and client (for decoding and execution). */ /** * Internal representation of a registered macro template */ interface MacroEntry { /** Unique macro identifier (0-255) */ macroId: number; /** Human-readable name (from template.id) */ name: string; /** Full template definition */ template: MacroTemplate; } /** * Internal representation of an active macro instance */ interface MacroInstanceEntry { /** Unique instance identifier (0-255) */ instanceId: number; /** Human-readable name */ name: string; /** Reference to macro template ID */ macroId: number; /** Reference to layer ID */ layerId: number; /** Position X (cells) */ x: number; /** Position Y (cells) */ y: number; /** Tab index for keyboard navigation */ tabIndex: number; /** Current parameters */ params: Record; } /** * Configuration for creating a macro instance */ interface CreateInstanceConfig { /** Instance name (will be mapped to numeric ID) */ id: string; /** Macro template name (must be loaded first) */ macro: string; /** Layer name (must exist) */ layer: string; /** Position X (cells) */ x: number; /** Position Y (cells) */ y: number; /** Tab index for keyboard navigation (0 = not focusable) */ tabIndex?: number; /** Instance parameters */ params?: Record; } /** * MacroRegistry - Manages macro templates and instances */ declare class MacroRegistry { private macros; private macroNameToId; private nextMacroId; private instances; private instanceNameToId; private nextInstanceId; private layerNameToId; private pendingOrders; /** * Register a macro template * * @param template - The macro template definition * @returns The assigned macro ID */ registerMacro(template: MacroTemplate): number; /** * Get macro template by name */ getMacroByName(name: string): MacroEntry | undefined; /** * Get macro template by ID */ getMacroById(macroId: number): MacroEntry | undefined; /** * Get macro ID by name */ getMacroId(name: string): number | undefined; /** * Generate MacroLoad packet for a template */ toMacroLoad(macroId: number): MacroLoad | undefined; /** * Generate all MacroLoad packets */ toAllMacroLoads(): MacroLoad[]; /** * Set layer name → ID mapping (called by User when layers change) */ setLayerMapping(layerName: string, layerId: number): void; /** * Create a new macro instance * Generates a CreateInstanceOrder for the next update packet * * @param config - Instance configuration * @returns The assigned instance ID, or undefined if macro/layer not found */ createInstance(config: CreateInstanceConfig): number | undefined; /** * Update an existing instance's parameters * * @param name - Instance name * @param params - Parameters to update (merged with existing) * @returns true if instance was found and updated */ updateInstance(name: string, params: Record): boolean; /** * Remove an instance * * @param name - Instance name * @returns true if instance was found and removed */ removeInstance(name: string): boolean; /** * Get instance by name */ getInstanceByName(name: string): MacroInstanceEntry | undefined; /** * Get instance by ID */ getInstanceById(instanceId: number): MacroInstanceEntry | undefined; /** * Get instance name by ID (for event handling) */ getInstanceName(instanceId: number): string | undefined; /** * Get pending orders and clear the queue * Called by UpdatePacketEncoder to include macro orders */ flushPendingOrders(): AnyMacroOrder[]; /** * Check if there are pending orders */ hasPendingOrders(): boolean; /** * Get all active instances */ getAllInstances(): MacroInstanceEntry[]; /** * Clear all instances (but keep templates) */ clearInstances(): void; /** * Clear everything (templates and instances) */ clear(): void; } /** * InputBindingRegistry - Input bindings registry * * Allows the server to define available axes and buttons, * and send them to the client via a JSON LoadPacket. * * Architecture: * 1. Server defines bindings (bindingId ↔ name) * 2. Server generates a JSON LoadPacket * 3. Client receives LoadPacket and configures its mappings * 4. Client sends back compressed inputs (bindingId + value) * 5. Server decodes with registry (bindingId → name → setAxis/setButton) */ /** * Input bindings registry * * Allows to: * - Define axes and buttons (bindingId → name) * - Define touch zones (virtual screen regions for mobile) * - Generate a JSON LoadPacket to send to client * - Decode compressed inputs received from client (future) */ declare class InputBindingRegistry { private axes; private buttons; private touchZones; private axisNameToId; private buttonNameToId; private touchZoneNameToId; private version; /** * Defines an axis binding * * @param bindingId - Unique axis ID (0-255) * @param name - Axis name (e.g., "MoveHorizontal", "CameraX") * @param sources - Physical sources (keyboard, gamepad, etc.) * @param min - Minimum axis value (default: -1.0) * @param max - Maximum axis value (default: +1.0) * @param defaultValue - Default axis value (default: 0.0) * @throws Error if bindingId or name already exists * * @example * ```typescript * import { InputDeviceType, KeyboardInput, GamepadInput } from '@utsp/types'; * * registry.defineAxis(0, "MoveHorizontal", [ * { sourceId: 0, type: InputDeviceType.Keyboard, negativeKey: KeyboardInput.ArrowLeft, positiveKey: KeyboardInput.ArrowRight }, * { sourceId: 1, type: InputDeviceType.Gamepad, gamepadIndex: 0, axis: GamepadInput.LeftStickX } * ], -1.0, 1.0, 0.0); * ``` */ defineAxis(bindingId: number, name: string, sources?: AxisSource[], min?: number, max?: number, defaultValue?: number): void; /** * Defines a button binding * * @param bindingId - Unique button ID (0-255) * @param name - Button name (e.g., "Jump", "Attack") * @param sources - Physical sources (keyboard, gamepad, mouse, touch) * @param defaultValue - Default button value (default: false) * @throws Error if bindingId or name already exists * * @example * ```typescript * import { InputDeviceType, KeyboardInput, GamepadInput } from '@utsp/types'; * * registry.defineButton(0, "Jump", [ * { sourceId: 0, type: InputDeviceType.Keyboard, key: KeyboardInput.Space }, * { sourceId: 1, type: InputDeviceType.Gamepad, gamepadIndex: 0, button: GamepadInput.A } * ], false); * ``` */ defineButton(bindingId: number, name: string, sources?: ButtonSource[], defaultValue?: boolean): void; /** * Defines a touch zone (virtual screen region for mobile touch input) * * Touch zones allow you to define regions of the screen that act as * virtual buttons or joysticks. Each zone has: * - A button state (pressed when any touch is in the zone) * - X/Y axis values (last touch position within the zone, normalized to cell coords) * * @param zoneId - Unique zone ID (0-31) * @param name - Zone name (e.g., "DPadUp", "ButtonA", "LeftJoystick") * @param x - X position in grid cells * @param y - Y position in grid cells * @param width - Width in grid cells * @param height - Height in grid cells * @throws Error if zoneId or name already exists * * @example * ```typescript * // D-pad buttons (bottom-left corner) * registry.defineTouchZone(0, "DPadUp", 2, 16, 4, 4); * registry.defineTouchZone(1, "DPadDown", 2, 24, 4, 4); * registry.defineTouchZone(2, "DPadLeft", 0, 20, 4, 4); * registry.defineTouchZone(3, "DPadRight", 6, 20, 4, 4); * * // Action button (bottom-right) * registry.defineTouchZone(4, "ButtonA", 74, 20, 6, 6); * * // Virtual joystick (large touch area) * registry.defineTouchZone(10, "LeftJoystick", 0, 12, 20, 16); * * // Then bind them in button/axis sources: * registry.defineButton(0, "Up", [ * { sourceId: 0, type: InputDeviceType.Keyboard, key: KeyboardInput.ArrowUp }, * { sourceId: 100, type: InputDeviceType.TouchZone, touchZoneId: 0 }, * ]); * ``` */ defineTouchZone(zoneId: number, name: string, x: number, y: number, width: number, height: number): void; /** * Evaluates an axis by summing all its sources * * This method takes raw values from each source (received from client) * and sums them according to the following logic: * 1. For each source: apply deadzone, scale, invert, sensitivity * 2. Sum all values * 3. Clamp between min and max * * Note: Raw source values are provided in sourceValues (Map) * * @param bindingId - Axis binding ID * @param sourceValues - Map of raw source values (sourceId → value) * @returns Final axis value (clamped), or defaultValue if binding not found * * @example * ```typescript * // Client sends: { sourceId: 0, value: -1.0 }, { sourceId: 1, value: 0.5 } * const sourceValues = new Map([[0, -1.0], [1, 0.5]]); * const axisValue = registry.evaluateAxis(0, sourceValues); * // Result: -1.0 + 0.5 = -0.5 (clamped between min and max) * ``` */ evaluateAxis(bindingId: number, sourceValues: Map): number; /** * Evaluates a button with OR logic on all its sources * * A button is considered pressed if AT LEAST ONE source is pressed. * * @param bindingId - Button binding ID * @param sourceValues - Map of source states (sourceId → pressed) * @returns true if at least one source is pressed, false otherwise * * @example * ```typescript * // Client sends: { sourceId: 0, pressed: false }, { sourceId: 1, pressed: true } * const sourceValues = new Map([[0, false], [1, true]]); * const buttonPressed = registry.evaluateButton(0, sourceValues); * // Result: true (because at least one source is pressed) * ``` */ evaluateButton(bindingId: number, sourceValues: Map): boolean; /** * Generates a LoadPacket JSON containing all bindings * * This packet will be sent to the client via the Load channel to indicate * which axes and buttons it should capture and send back. * * JSON format (not binary for now as sent rarely) * * @returns LoadPacket JSON as string * * @example * ```typescript * const packet = registry.toLoadPacket(); * websocket.send(packet); // Send to client * ``` */ toLoadPacket(): string; /** * Generates a LoadPacket JSON as object * (useful for inspection or tests) * * @returns LoadPacket as object */ toLoadPacketObject(): InputBindingLoadPacket; /** * Retrieves an axis bindingId from its name * * @param name - Axis name * @returns bindingId or null if not found * * @example * ```typescript * const id = registry.getAxisBindingId("MoveHorizontal"); * if (id !== null) { * console.log(`MoveHorizontal has bindingId ${id}`); * } * ``` */ getAxisBindingId(name: string): number | null; /** * Retrieves a button bindingId from its name * * @param name - Button name * @returns bindingId or null if not found * * @example * ```typescript * const id = registry.getButtonBindingId("Jump"); * if (id !== null) { * console.log(`Jump has bindingId ${id}`); * } * ``` */ getButtonBindingId(name: string): number | null; /** * Retrieves an axis name from its bindingId * * @param bindingId - Binding ID * @returns name or null if not found * * @example * ```typescript * const name = registry.getAxisName(0); * console.log(name); // "MoveHorizontal" * ``` */ getAxisName(bindingId: number): string | null; /** * Retrieves a button name from its bindingId * * @param bindingId - Binding ID * @returns name or null if not found * * @example * ```typescript * const name = registry.getButtonName(0); * console.log(name); // "Jump" * ``` */ getButtonName(bindingId: number): string | null; /** * Retrieves a complete axis binding * * @param bindingId - Binding ID * @returns binding or null if not found */ getAxisBinding(bindingId: number): AxisBinding | null; /** * Retrieves a complete button binding * * @param bindingId - Binding ID * @returns binding or null if not found */ getButtonBinding(bindingId: number): ButtonBinding | null; /** * Checks if an axis is defined * * @param bindingId - Binding ID * @returns true if defined */ hasAxis(bindingId: number): boolean; /** * Checks if a button is defined * * @param bindingId - Binding ID * @returns true if defined */ hasButton(bindingId: number): boolean; /** * Checks if a touch zone is defined * * @param zoneId - Zone ID * @returns true if defined */ hasTouchZone(zoneId: number): boolean; /** * Counts the number of defined axes * * @returns number of axes */ getAxisCount(): number; /** * Counts the number of defined buttons * * @returns number of buttons */ getButtonCount(): number; /** * Counts the number of defined touch zones * * @returns number of touch zones */ getTouchZoneCount(): number; /** * Retrieves all axes * * @returns array of all axis bindings */ getAllAxes(): AxisBinding[]; /** * Retrieves all buttons * * @returns array of all button bindings */ getAllButtons(): ButtonBinding[]; /** * Retrieves all touch zones * * @returns array of all touch zone bindings */ getAllTouchZones(): TouchZoneBinding[]; /** * Retrieves a touch zone by ID * * @param zoneId - Zone ID * @returns zone binding or null if not found */ getTouchZone(zoneId: number): TouchZoneBinding | null; /** * Retrieves a touch zone ID from its name * * @param name - Zone name * @returns zoneId or null if not found */ getTouchZoneId(name: string): number | null; /** * Retrieves the current binding version * * @returns version (incremented with each modification) */ getVersion(): number; /** * Removes an axis * * @param bindingId - Binding ID to remove * @returns true if removed, false if not found */ removeAxis(bindingId: number): boolean; /** * Removes a button * * @param bindingId - Binding ID to remove * @returns true if removed, false if not found */ removeButton(bindingId: number): boolean; /** * Removes a touch zone * * @param zoneId - Zone ID to remove * @returns true if removed, false if not found */ removeTouchZone(zoneId: number): boolean; /** * Removes all axes */ clearAxes(): void; /** * Removes all buttons */ clearButtons(): void; /** * Removes all touch zones */ clearTouchZones(): void; /** * Completely resets the registry */ clear(): void; /** * Displays a summary of bindings (for debug) * * @returns formatted string */ toString(): string; } declare class UserStats { private enabled; private currentStats; private _userId; private _userName; constructor(userId: string, userName: string); /** * Enables or disables statistics collection. * * @param enabled - Whether to collect stats. * * @example * ```typescript * user.getStats().setEnabled(true); * ``` */ setEnabled(enabled: boolean): void; /** * Returns whether statistics collection is enabled. * * @returns true if enabled. */ isEnabled(): boolean; /** * Returns the user ID. * * @returns User ID. * * @example * ```typescript * const id = user.getStats().userId; * ``` */ get userId(): string; /** * Returns the user name. * * @returns User name. * * @example * ```typescript * const name = user.getStats().userName; * ``` */ get userName(): string; /** * Returns the current tick number. * * @returns Tick number. * * @example * ```typescript * const tick = user.getStats().tick; * ``` */ get tick(): number; /** * Returns the current tick timestamp. * * @returns Timestamp in ms. * * @example * ```typescript * const ts = user.getStats().timestamp; * ``` */ get timestamp(): number; /** * Returns the number of displays. * * @returns Display count. * * @example * ```typescript * const count = user.getStats().displayCount; * ``` */ get displayCount(): number; /** * Returns the total display surface area (width × height). * * @returns Total display area. * * @example * ```typescript * const area = user.getStats().totalDisplayArea; * ``` */ get totalDisplayArea(): number; /** * Returns the total number of layers. * * @returns Total layer count. * * @example * ```typescript * const total = user.getStats().totalLayers; * ``` */ get totalLayers(): number; /** * Returns the number of visible layers in displays. * * @returns Visible layer count. * * @example * ```typescript * const visible = user.getStats().visibleLayers; * ``` */ get visibleLayers(): number; /** * Returns the number of reliable layers. * * @returns Reliable layer count. * * @example * ```typescript * const reliable = user.getStats().staticLayers; * ``` */ get staticLayers(): number; /** * Returns the number of volatile layers. * * @returns Volatile layer count. * * @example * ```typescript * const volatile = user.getStats().dynamicLayers; * ``` */ get dynamicLayers(): number; /** * Returns the total number of orders for this user. * * @returns Total order count. * * @example * ```typescript * const orders = user.getStats().totalOrders; * ``` */ get totalOrders(): number; /** * Returns order counts per layer ID. * * @returns Map of layerId → order count. * * @example * ```typescript * const byLayer = user.getStats().ordersByLayer; * const uiOrders = byLayer.get(2) ?? 0; * ``` */ get ordersByLayer(): Map; /** * Returns the static packet size in bytes. * * @returns Static packet size. * * @example * ```typescript * const size = user.getStats().staticPacketSize; * ``` */ get staticPacketSize(): number; /** * Returns the dynamic packet size in bytes. * * @returns Dynamic packet size. * * @example * ```typescript * const size = user.getStats().dynamicPacketSize; * ``` */ get dynamicPacketSize(): number; /** * Returns the total packet size (static + dynamic) in bytes. * * @returns Total packet size. * * @example * ```typescript * const total = user.getStats().totalPacketSize; * ``` */ get totalPacketSize(): number; /** * Returns the estimated compressed size (25% of original). * * @returns Estimated compressed size. * * @example * ```typescript * const compressed = user.getStats().compressedPacketSize; * ``` */ get compressedPacketSize(): number; /** * Returns whether the user sent input this tick. * * @returns true if input was sent. * * @example * ```typescript * if (user.getStats().hasInput) { * console.log('Input received this tick'); * } * ``` */ get hasInput(): boolean; /** * Returns the number of bound axes. * * @returns Axis count. * * @example * ```typescript * const axes = user.getStats().axisCount; * ``` */ get axisCount(): number; /** * Returns the number of bound buttons. * * @returns Button count. * * @example * ```typescript * const buttons = user.getStats().buttonCount; * ``` */ get buttonCount(): number; /** * Starts collection for a new tick. * @internal */ startTick(tickNumber: number): void; /** * Records display information. * @internal */ recordDisplays(displayCount: number, totalArea: number): void; /** * Records layer information. * @internal */ recordLayers(total: number, visible: number, staticCount: number, dynamicCount: number): void; /** * Records order count for a layer. * @internal */ recordLayerOrders(layerId: number, orderCount: number): void; /** * Records network packet sizes. * @internal */ recordPacketSizes(staticSize: number, dynamicSize: number): void; /** * Records input information. * @internal */ recordInput(hasInput: boolean, axisCount: number, buttonCount: number): void; /** * Finalizes current tick stats. * @internal */ endTick(): void; /** * Resets statistics. * * @example * ```typescript * user.getStats().reset(); * ``` */ reset(): void; } /** * Represents a connected user with their displays and layers * * ARCHITECTURE (new protocol): * - LAYERS are at the USER level (shared across all displays) * - DISPLAYS are origins (cameras) that look into the world * - Each display can see the same layers (if within its origin) * * @template TData - Application-specific data type (default: Record) */ declare class User> { id: string; name: string; private displays; private layers; private spriteRegistry; private mode; private axes; private buttons; private textInputs; private mouseX; private mouseY; private mouseOver; private mouseDisplayId; isTabHidden: boolean; private touchPositions; private activeTouchId; private inputBindings; private stats; private soundCommands; private nextSoundInstanceId; private audioConfigCommands; private lastListenerX; private lastListenerY; private pendingSendSounds; private audioProcessor?; private loadedSounds; private soundLoadErrors; private playingSounds; private mobileVibrationCommands; private mobileVibrationProcessor?; private gamepadVibrationCommands; private gamepadVibrationProcessor?; private macroRegistry; private macroEngine; private macroEventHandlers; private postProcessCommands; private currentPostProcessConfig; private bridgeMessages; private totalBytesSent; private totalBytesReceived; private bytesSentPerTick; private bytesReceivedPerTick; private bytesTickIndex; private bytesTickRate; private currentTickBytesSent; private currentTickBytesReceived; private lastInputTick; private availableViewports; /** * Display command sink (used by Display to enqueue network commands) */ private readonly displayCommandSink; /** * Application-specific data storage * Use this to store game state, player data, or any custom information */ data: TData; constructor(id: string, name: string, mode: CoreMode); /** * Injects SpriteRegistry into the user (called by Core) * @internal */ setSpriteRegistry(registry: SpriteRegistry): void; /** * Returns all displays for this user. * * @returns Array of displays. * * @example * ```typescript * const displays = user.getDisplays(); * const mainDisplay = displays[0]; * ``` */ getDisplays(): Display[]; /** * Adds a display to the user * * @param display - The display to add * * @example * ```typescript * const display = new Display(0, 80, 25); * user.addDisplay(display); * ``` */ addDisplay(display: Display): void; /** * Removes a display from the user * * @param display - The display to remove * @returns true if display was removed, false otherwise * * @example * ```typescript * const ok = user.removeDisplay(display); * if (!ok) console.warn('Display not found'); * ``` */ removeDisplay(display: Display): boolean; /** * Removes all displays from the user * * @example * ```typescript * user.clearDisplays(); * ``` */ clearDisplays(): void; /** * Sets the available viewport size (in pixels) for a display * * Called by the server when receiving viewport information from the client. * The application can use this to adapt display resolution. * * @param displayId - Display ID (0-255) * @param pixelWidth - Available width in pixels (0-65535) * @param pixelHeight - Available height in pixels (0-65535) * * @example * ```typescript * // Called automatically by decodeAndApplyCompressedInput() * user.setDisplayViewport(0, 800, 600); * ``` */ setDisplayViewport(displayId: number, pixelWidth: number, pixelHeight: number): void; /** * Gets the available viewport size (in pixels) for a display * * @param displayId - Display ID (0-255) * @returns Viewport size in pixels, or null if not set * * @example * ```typescript * const viewport = user.getDisplayViewport(0); * if (viewport) { * console.log(`Available: ${viewport.pixelWidth}x${viewport.pixelHeight}px`); * } * ``` */ getDisplayViewport(displayId: number): { pixelWidth: number; pixelHeight: number; } | null; /** * Gets all available viewports (in pixels) * * @returns Map of displayId → viewport size * * @example * ```typescript * const viewports = user.getAllDisplayViewports(); * const display0 = viewports.get(0); * ``` */ getAllDisplayViewports(): Map; /** * Calculates the maximum number of cells that fit in a display's available viewport * * Helper method that divides pixel dimensions by cell size to get max cells. * The application can use this to decide the optimal display resolution. * * @param displayId - Display ID (0-255) * @param cellWidth - Width of a cell in pixels (e.g., 8 for 8px font) * @param cellHeight - Height of a cell in pixels (e.g., 16 for 16px font) * @returns Max cells { cols, rows }, or null if viewport not set * * @example * ```typescript * // With 8x16 font, calculate how many cells fit in available space * const maxCells = user.calculateMaxCells(0, 8, 16); * if (maxCells) { * const display = user.getDisplays()[0]; * display.setSize(new Vector2( * Math.min(maxCells.cols, 120), // Cap at 120 columns * Math.min(maxCells.rows, 40) // Cap at 40 rows * )); * } * ``` */ calculateMaxCells(displayId: number, cellWidth: number, cellHeight: number): { cols: number; rows: number; } | null; /** * Returns all layers for this user. * * @returns Array of layers. * * @example * ```typescript * const layers = user.getLayers(); * const uiLayer = layers.find((l) => l.getName() === 'ui'); * ``` */ getLayers(): Layer[]; /** * Gets a layer by its ID * * @param layerId - The layer ID to find * @returns The layer, or undefined if not found * * @example * ```typescript * const layer = user.getLayerById(2); * if (layer) layer.setZOrder(10); * ``` */ getLayerById(layerId: number): Layer | undefined; /** * Adds a layer to the user * Layers are shared across all displays * * @param layer - The layer to add * @param name - Optional name for macro system (used in createInstance) * * @example * ```typescript * const layer = new Layer(new Vector2(0, 0), 0, 80, 25); * user.addLayer(layer, 'game'); * ``` */ addLayer(layer: Layer, name?: string): void; /** * Removes a layer from the user * * @param layer - The layer to remove * @returns true if layer was removed, false otherwise * * @example * ```typescript * user.removeLayer(layer); * ``` */ removeLayer(layer: Layer): boolean; /** * Removes all layers from the user * * @example * ```typescript * user.clearLayers(); * ``` */ clearLayers(): void; /** * Updates mouse position (called by client) * * @param x - X position (0-255) * @param y - Y position (0-255) * @param over - Is mouse over the display? * * @example * ```typescript * user.setMousePosition(128, 64, true); * ``` */ setMousePosition(x: number, y: number, over?: boolean): void; /** * Sets the position of a touch (multi-touch support) * * @param touchId - Touch ID (0-9) * @param x - X position in display (0-255) * @param y - Y position in display (0-255) * @param over - If touch is active (true) or released (false) * @param displayId - ID of concerned display (0-255) * * @example * ```typescript * // Touch ID 0 at position (128, 64) * user.setTouchPosition(0, 128, 64, true, 0); * ``` */ setTouchPosition(touchId: number, x: number, y: number, over?: boolean, displayId?: number): void; /** * Sets the value of a virtual axis * * Axes are floating point values between -1.0 and +1.0, typically used for: * - Movement (Horizontal, Vertical) * - Camera rotation (CameraX, CameraY) * - Analog controls (Throttle, Steering) * * Axis name is free-form and defined by the application. * Physical mapping (keyboard, gamepad, etc.) is handled by the client. * * @param axisName - Axis name (e.g., "Horizontal", "Vertical", "CameraX") * @param value - Axis value (-1.0 to +1.0) * * @example * ```typescript * // Client side: map keys to axes * user.setAxis("Horizontal", keyboard.arrowLeft ? -1.0 : keyboard.arrowRight ? 1.0 : 0.0); * user.setAxis("Vertical", keyboard.arrowUp ? -1.0 : keyboard.arrowDown ? 1.0 : 0.0); * user.setAxis("CameraX", gamepad.rightStickX); // -1.0 to +1.0 * ``` */ setAxis(axisName: string, value: number): void; /** * Gets the value of a virtual axis * * @param axisName - Axis name (e.g., "Horizontal", "Vertical") * @returns Axis value (-1.0 to +1.0), or 0.0 if axis doesn't exist * * @example * ```typescript * // Server side: use axes for game logic * const moveX = user.getAxis("Horizontal"); * const moveY = user.getAxis("Vertical"); * player.move(moveX, moveY); * ``` */ getAxis(axisName: string): number; /** * Sets the state of a virtual button * * Buttons are boolean values used for actions: * - Point actions (Jump, Fire, Interact) * - Continuous states (Run, Crouch, Aim) * * Button name is free-form and defined by the application. * Physical mapping (keyboard, gamepad, etc.) is handled by the client. * * @param buttonName - Button name (e.g., "Jump", "Fire", "Inventory") * @param pressed - Button state (true = pressed, false = released) * * @example * ```typescript * // Client side: map keys to buttons * user.setButton("Jump", keyboard.space || mouse.leftClick); * user.setButton("Fire", mouse.rightClick); * user.setButton("Inventory", keyboard.i); * user.setButton("Sprint", keyboard.shift); * ``` */ setButton(buttonName: string, pressed: boolean): void; /** * Checks if a virtual button is pressed * * @param buttonName - Button name (e.g., "Jump", "Fire") * @returns true if button is pressed, false otherwise * * @example * ```typescript * // Server side: use buttons for game logic * if (user.getButton("Jump")) { * player.jump(); * } * * if (user.getButton("Fire")) { * player.shoot(); * } * * if (user.getButton("Sprint")) { * player.speed = 2.0; * } else { * player.speed = 1.0; * } * ``` */ getButton(buttonName: string): boolean; /** * Checks if a virtual button was just pressed this frame * * @param buttonName - Button name (e.g., "Jump", "Fire") * @returns true if button just transitioned from false→true * * @example * ```typescript * // Server side: single-shot actions * if (user.getButtonJustPressed("Jump")) { * player.jump(); // Fires once per press, not continuously * } * * if (user.getButtonJustPressed("ToggleInventory")) { * ui.toggleInventory(); // Won't toggle 5 times per click * } * ``` */ getButtonJustPressed(buttonName: string): boolean; /** * Checks if a virtual button was just released this frame * * @param buttonName - Button name (e.g., "Fire", "ChargeShot") * @returns true if button just transitioned from true→false * * @example * ```typescript * // Server side: charge and release mechanics * if (user.getButton("ChargeShot")) { * weapon.charge(); // Accumulate power while held * } * if (user.getButtonJustReleased("ChargeShot")) { * weapon.fireChargedShot(); // Release when button released * } * ``` */ getButtonJustReleased(buttonName: string): boolean; /** * Sets text input events for this frame * Called internally by input decoding system * * @param inputs - Array of key strings (e.g., ['a', 'Backspace', 'Enter']) * @internal */ setTextInputs(inputs: string[]): void; /** * Gets text input events for this frame * Use this in your update loop to handle input boxes, chat, etc. * * @returns Array of key strings typed this frame * * @example * ```typescript * // Server side: handle input box * const textInputs = user.getTextInputs(); * for (const key of textInputs) { * if (key.length === 1) { * inputBox.addChar(key); // Regular character * } else if (key === 'Backspace') { * inputBox.deleteChar(); * } else if (key === 'Enter') { * inputBox.submit(); * } * } * ``` */ getTextInputs(): string[]; /** * Clears text input events (called after frame processing) * @internal */ clearTextInputs(): void; /** * Gets all defined axis names * * @returns Array of axis names * * @example * ```typescript * const axisNames = user.getAxisNames(); * console.log(axisNames); // ["Horizontal", "Vertical", "CameraX", "CameraY"] * ``` */ getAxisNames(): string[]; /** * Gets all defined button names * * @returns Array of button names * * @example * ```typescript * const buttonNames = user.getButtonNames(); * console.log(buttonNames); // ["Jump", "Fire", "Inventory", "Interact"] * ``` */ getButtonNames(): string[]; /** * Resets all axes to 0.0 * * @example * ```typescript * user.clearAxes(); * console.log(user.getAxis("Horizontal")); // 0.0 * ``` */ clearAxes(): void; /** * Resets all buttons to false * * @example * ```typescript * user.clearButtons(); * console.log(user.getButton("Jump")); // false * ``` */ clearButtons(): void; /** * Gets detailed information about mouse position * * Returns mouse position in multiple coordinate spaces: * - displayId: ID of the hovered display (or null if none) * - localX/localY: Position in the display (0 to sizeX/sizeY) * - worldX/worldY: Position in the virtual world (origin + local) * * Note: For now, we consider the mouse always hovers display 0 * if mouseOver is true and at least one display exists. * * @returns Mouse position information, or null if no display * * @example * ```typescript * const mouseInfo = user.getMouseDisplayInfo(); * if (mouseInfo) { * console.log(`Mouse over display ${mouseInfo.displayId}`); * console.log(`Local: ${mouseInfo.localX}, ${mouseInfo.localY}`); * console.log(`World: ${mouseInfo.worldX}, ${mouseInfo.worldY}`); * } * ``` */ getMouseDisplayInfo(): { displayId: number | null; localX: number; localY: number; worldX: number; worldY: number; } | null; /** * Gets position information for a touch relative to the display * * @param touchId - Touch ID (0-9), default = 0 (first finger) * @returns Touch information (displayId, localX, localY, worldX, worldY) or null * * @example * ```typescript * const touchInfo = user.getTouchDisplayInfo(0); * if (touchInfo) { * console.log(`Touch 0 over display ${touchInfo.displayId}`); * console.log(`Local: ${touchInfo.localX}, ${touchInfo.localY}`); * console.log(`World: ${touchInfo.worldX}, ${touchInfo.worldY}`); * } * ``` */ getTouchDisplayInfo(touchId?: number): { displayId: number | null; localX: number; localY: number; worldX: number; worldY: number; } | null; /** * Checks if the mouse is currently over a display * * @returns true if mouse is hovering a display, false otherwise * * @example * ```typescript * if (user.getIsMouseOnADisplay()) { * const info = user.getMouseDisplayInfo(); * console.log(`Hovering display ${info.displayId}`); * } * ``` */ getIsMouseOnADisplay(): boolean; /** * Gets the ID of the display currently hovered by the mouse * * @returns Display ID (0-255) or null if no display is hovered * * @example * ```typescript * const hoveredDisplayId = user.getMouseDisplayHover(); * if (hoveredDisplayId !== null) { * console.log(`Hovering display ${hoveredDisplayId}`); * } * ``` */ getMouseDisplayHover(): number | null; /** * Finds a layer by its ID * * @param id - ID of the layer to find * @returns Found layer or null * @internal - Used for UpdatePacket reconstruction */ private findLayerById; /** * Applies an UpdatePacket to this user (CLIENT-SIDE RECONSTRUCTION) * * This method reconstructs the user state from an UpdatePacket * received from the server. It updates displays (viewports) and layers * incrementally according to updateFlags. * * IMPORTANT: This method must be called in CLIENT mode only. * The server generates packets, the client applies them. * * @param packet - Decoded UpdatePacket received from server * * @example * ```typescript * // Client side (ClientRuntime) * const decoder = new UpdatePacketDecoder(); * const packet = decoder.decode(receivedBuffer); * user.applyUpdate(packet); * ``` */ applyUpdate(packet: UpdatePacket): void; /** * Updates displays from an UpdatePacket * * Adjusts the number of displays and updates their origins/sizes. * * @private * @param networkDisplays - Displays from packet */ private updateDisplaysFromPacket; /** * Updates layers from an UpdatePacket (INCREMENTAL LOGIC) * * This method reconstructs layers according to updateFlags: * - 0x01: Update origin * - 0x02: Update z-order * - 0x04: Update orders (replace or add according to SET/ADD flag) * * If a layer doesn't exist yet, it is created automatically. * * IMPORTANT: In client mode, orders are automatically rasterized * when calling setOrders() / addOrders(). * * @private * @param networkLayers - Layers from packet with their updateFlags */ private updateLayersFromPacket; /** * Defines an axis binding * * Associates a bindingId (0-255) with an axis name and its physical sources. * The client will receive this mapping via a JSON LoadPacket. * * Sources are summed on the server side after receiving raw values. * * @param bindingId - Unique axis ID (0-255) * @param name - Axis name (e.g., "MoveHorizontal", "CameraX") * @param sources - Physical sources (keyboard, gamepad, mouse, gyro, touch) * @param min - Minimum value (default: -1.0) * @param max - Maximum value (default: +1.0) * @param defaultValue - Default value (default: 0.0) * * @example * ```typescript * import { InputDeviceType, KeyboardInput, GamepadInput } from '@utsp/types'; * * user.defineAxisBinding(0, "MoveHorizontal", [ * { sourceId: 0, type: InputDeviceType.Keyboard, negativeKey: KeyboardInput.ArrowLeft, positiveKey: KeyboardInput.ArrowRight }, * { sourceId: 1, type: InputDeviceType.Gamepad, gamepadIndex: 0, axis: GamepadInput.LeftStickX } * ], -1.0, 1.0, 0.0); * ``` */ defineAxisBinding(bindingId: number, name: string, sources?: AxisSource[], min?: number, max?: number, defaultValue?: number): void; /** * Defines a button binding * * Associates a bindingId (0-255) with a button name and its physical sources. * Sources use OR logic (at least one pressed source = button pressed). * * @param bindingId - Unique button ID (0-255) * @param name - Button name (e.g., "Jump", "Attack") * @param sources - Physical sources (keyboard, gamepad, mouse, touch) * @param defaultValue - Default value (default: false) * * @example * ```typescript * import { InputDeviceType, KeyboardInput, GamepadInput } from '@utsp/types'; * * user.defineButtonBinding(0, "Jump", [ * { sourceId: 0, type: InputDeviceType.Keyboard, key: KeyboardInput.Space }, * { sourceId: 1, type: InputDeviceType.Gamepad, gamepadIndex: 0, button: GamepadInput.A } * ], false); * ``` */ defineButtonBinding(bindingId: number, name: string, sources?: ButtonSource[], defaultValue?: boolean): void; /** * Defines multiple axis bindings at once * * @param axes - Array of axis definitions * * @example * ```typescript * import { InputDeviceType, KeyboardInput } from '@utsp/types'; * * user.defineAxisBindings([ * { * bindingId: 0, * name: "MoveHorizontal", * sources: [ * { sourceId: 0, type: InputDeviceType.Keyboard, negativeKey: KeyboardInput.ArrowLeft, positiveKey: KeyboardInput.ArrowRight } * ] * }, * { bindingId: 1, name: "MoveVertical", sources: [] }, * ]); * ``` */ defineAxisBindings(axes: Array<{ bindingId: number; name: string; sources?: AxisSource[]; min?: number; max?: number; defaultValue?: number; }>): void; /** * Defines multiple button bindings at once * * @param buttons - Array of button definitions * * @example * ```typescript * import { InputDeviceType, KeyboardInput } from '@utsp/types'; * * user.defineButtonBindings([ * { * bindingId: 0, * name: "Jump", * sources: [ * { sourceId: 0, type: InputDeviceType.Keyboard, key: KeyboardInput.Space } * ] * }, * { bindingId: 1, name: "Attack", sources: [] }, * ]); * ``` */ defineButtonBindings(buttons: Array<{ bindingId: number; name: string; sources?: ButtonSource[]; defaultValue?: boolean; }>): void; /** * Generates the JSON LoadPacket containing input bindings * * This packet must be sent to the client via the Load channel to indicate * which axes and buttons it should capture and send back. * * Format: JSON string (not binary for now as it's sent rarely) * * @returns JSON LoadPacket as string * * @example * ```typescript * // Server: Define bindings * user.defineAxisBinding(0, "MoveHorizontal"); * user.defineAxisBinding(1, "MoveVertical"); * user.defineButtonBinding(0, "Jump"); * * // Generate and send to client * const packet = user.getInputBindingsLoadPacket(); * websocket.send(packet); * ``` */ getInputBindingsLoadPacket(): string; /** * Applies Input Bindings received from server (CLIENT-SIDE) * * This method parses the bindings JSON and configures them in the registry. * The client can then capture inputs and send them compressed. * * @param json - JSON string of bindings from getInputBindingsLoadPacket() * * @example * ```typescript * // Client side * websocket.on('input-bindings', (json: string) => { * user.applyInputBindingsLoadPacket(json); * console.log('Input bindings configured'); * }); * ``` */ applyInputBindingsLoadPacket(json: string): void; /** * Gets this user's InputBindingRegistry * * @returns InputBindingRegistry instance * * @example * ```typescript * const registry = user.getInputBindingRegistry(); * console.log(registry.toString()); * console.log(`Axes: ${registry.getAxisCount()}`); * console.log(`Buttons: ${registry.getButtonCount()}`); * ``` */ getInputBindingRegistry(): InputBindingRegistry; /** * Gets the UserStats object to access this user's statistics * * @returns The user's UserStats instance * * @example * ```typescript * // Enable stats * user.getStats().setEnabled(true); * * // After a tick... * const stats = user.getStats(); * console.log(`User ${stats.userName}:`); * console.log(` Displays: ${stats.displayCount}`); * console.log(` Layers: ${stats.visibleLayers}/${stats.totalLayers}`); * console.log(` Orders: ${stats.totalOrders}`); * console.log(` Packet size: ${stats.totalPacketSize} bytes`); * ``` */ getStats(): UserStats; /** * Gets total bytes sent to this user (server-side) * Excludes bridge messages. * * @returns Total bytes sent. * * @example * ```typescript * const total = user.getTotalBytesSent(); * console.log(`Sent: ${total} bytes`); * ``` */ getTotalBytesSent(): number; /** * Gets total bytes received by this user (client-side) * Excludes bridge messages. * * @returns Total bytes received. * * @example * ```typescript * const total = user.getTotalBytesReceived(); * console.log(`Received: ${total} bytes`); * ``` */ getTotalBytesReceived(): number; /** * Records bytes sent to this user (called by ServerRuntime) * @internal */ recordBytesSent(bytes: number): void; /** * Records bytes received by this user (called by NetworkSync) * @internal */ recordBytesReceived(bytes: number): void; /** * Resets byte counters (useful for periodic stats) * * @example * ```typescript * user.resetByteCounters(); * ``` */ resetByteCounters(): void; /** * Sets the tick rate for bytes-per-second calculation * Call this when the runtime tick rate changes * @param tickRate - The tick rate in ticks per second * * @example * ```typescript * user.setBytesTickRate(60); * ``` */ setBytesTickRate(tickRate: number): void; /** * Called at end of each tick to record bytes for this tick * Updates the sliding window for bytes/sec calculation * @internal */ endTickBytes(): void; /** * Gets bytes sent per second (sliding window over last second) * @returns Bytes sent per second * * @example * ```typescript * const sentPerSec = user.getBytesSentPerSecond(); * ``` */ getBytesSentPerSecond(): number; /** * Gets bytes received per second (sliding window over last second) * @returns Bytes received per second * * @example * ```typescript * const recvPerSec = user.getBytesReceivedPerSecond(); * ``` */ getBytesReceivedPerSecond(): number; /** * Gets the bindingId of an axis from its name * * @param name - Axis name * @returns bindingId or null if not found * * @example * ```typescript * const id = user.getAxisBindingId('MoveHorizontal'); * ``` */ getAxisBindingId(name: string): number | null; /** * Gets the bindingId of a button from its name * * @param name - Button name * @returns bindingId or null if not found * * @example * ```typescript * const id = user.getButtonBindingId('Jump'); * ``` */ getButtonBindingId(name: string): number | null; /** * Decodes and applies a compressed input buffer received from client (format v5) * * This method: * 1. Decodes the binary buffer (v5 MINIMAL format - without counts) * 2. Stores values in axes/buttons Maps * 3. Updates mouse position + displayId * * Format v5: Buffer does NOT contain axes/buttons counts. * Server uses its internal registry to know the expected structure. * * IMPORTANT: Client and server must have the SAME bindings defined! * * @param buffer - Encoded buffer received from client via WebSocket * * @example * ```typescript * // Server receives buffer from client * websocket.on('message', (data) => { * const buffer = new Uint8Array(data); * user.decodeAndApplyCompressedInput(buffer); * * // Values are now available * const move = user.getAxis("MoveHorizontal"); * if (user.getButton("Jump")) { ... } * * // Viewport info is available via: * const viewport = user.getDisplayViewport(0); * if (viewport) { * console.log(`Available: ${viewport.pixelWidth}x${viewport.pixelHeight}px`); * } * }); * ``` */ decodeAndApplyCompressedInput(buffer: Uint8Array): void; /** * Play a sound for this user * * The sound command is queued and will be sent to the client * at the end of the current tick by the runtime. * * @param sound - Sound name (e.g., 'coin') or ID (0-255) * @param options - Playback options (volume, pitch, loop, fadeIn, position) * @returns Instance ID that can be used to stop this specific sound later * * @example * ```typescript * // In updateUser() * if (playerCollectedCoin) { * user.playSound('coin'); * } * * // With options * user.playSound('explosion', { volume: 0.8, pitch: 0.9 }); * * // Loop background music with fade in * const musicId = user.playSound('music', { loop: true, volume: 0.5, fadeIn: 2 }); * // Later: user.fadeOutSound(musicId, 2); * * // Spatial audio (x/y position 0-65535) * user.playSound('explosion', { x: 10000, y: 32768 }); * * // With audio effects * user.playSound('voice', { lowpass: 2000, reverb: 0.3 }); * ``` */ playSound(sound: string | number, options?: { volume?: number; pitch?: number; loop?: boolean; fadeIn?: number; x?: number; y?: number; /** Low-pass filter cutoff in Hz (100-25500, 0 = disabled) */ lowpass?: number; /** High-pass filter cutoff in Hz (100-25500, 0 = disabled) */ highpass?: number; /** Reverb wet/dry mix (0.0-1.0, 0 = disabled) */ reverb?: number; }): SoundInstanceId; /** * Stop a sound immediately by instance ID or name * * @param target - Instance ID (number) to stop specific instance, * or sound name (string) to stop all instances of that sound * * @example * ```typescript * // Stop specific instance by ID * const musicId = user.playSound('music', { loop: true }); * user.stopSound(musicId); * * // Stop all instances of a sound by name * user.stopSound('explosion'); * ``` */ stopSound(target: SoundInstanceId | string): void; /** * Fade out and stop a sound * * @param target - Instance ID (number) to fade specific instance, * or sound name (string) to fade all instances of that sound * @param duration - Fade duration in seconds * * @example * ```typescript * // Fade out specific instance over 2 seconds * const musicId = user.playSound('music', { loop: true, fadeIn: 1 }); * // Later, when changing scene: * user.fadeOutSound(musicId, 2); * * // Fade out all ambient sounds * user.fadeOutSound('ambient', 1.5); * ``` */ fadeOutSound(target: SoundInstanceId | string, duration: number): void; /** * Fade out and stop all sounds for this user * * @param duration - Fade duration in seconds * * @example * ```typescript * // When entering a cutscene, fade out all game audio * user.fadeOutAllSounds(1); * ``` */ fadeOutAllSounds(duration: number): void; /** * Stop all sounds immediately for this user * * @example * ```typescript * // When player dies or game pauses * user.stopAllSounds(); * ``` */ stopAllSounds(): void; /** * Pause a sound by instance ID or name * * @param target - Instance ID (number) to pause specific instance, * or sound name (string) to pause all instances of that sound * * @example * ```typescript * // Pause specific instance * const musicId = user.playSound('music', { loop: true }); * user.pauseSound(musicId); * // Later: user.resumeSound(musicId); * * // Pause all ambient sounds * user.pauseSound('ambient'); * ``` */ pauseSound(target: SoundInstanceId | string): void; /** * Pause all sounds for this user * * @example * ```typescript * // When opening pause menu * user.pauseAllSounds(); * ``` */ pauseAllSounds(): void; /** * Resume a paused sound by instance ID or name * * @param target - Instance ID (number) to resume specific instance, * or sound name (string) to resume all instances of that sound * * @example * ```typescript * // Resume specific instance * user.resumeSound(musicId); * * // Resume all ambient sounds * user.resumeSound('ambient'); * ``` */ resumeSound(target: SoundInstanceId | string): void; /** * Resume all paused sounds for this user * * @example * ```typescript * // When closing pause menu * user.resumeAllSounds(); * ``` */ resumeAllSounds(): void; /** * Set audio effects on a playing sound instance * * This allows updating filters and reverb on a sound that's already playing. * Pass 0 to disable a specific effect, or omit it to leave unchanged. * * @param instanceId - The instance ID returned by playSound() * @param options - Effects to set * * @example * ```typescript * const musicId = user.playSound('music', { loop: true }); * * // Later, add effects when entering a cave * user.setSoundEffects(musicId, { lowpass: 1500, reverb: 0.5 }); * * // Remove effects when leaving the cave * user.setSoundEffects(musicId, { lowpass: 0, reverb: 0 }); * ``` */ setSoundEffects(instanceId: SoundInstanceId, options: { /** Low-pass filter cutoff in Hz (100-25500, 0 = disabled) */ lowpass?: number; /** High-pass filter cutoff in Hz (100-25500, 0 = disabled) */ highpass?: number; /** Reverb wet/dry mix (0.0-1.0, 0 = disabled) */ reverb?: number; }): void; /** * Set the listener position for spatial audio * * The listener is the "ear" of the player. Sounds with positions * will be panned and attenuated based on their distance to the listener. * * @param x - X position (0-65535, typically matches player position) * @param y - Y position (0-65535, typically matches player position) * * @example * ```typescript * // In updateUser(), follow the player * user.setListenerPosition(player.x, player.y); * * // Play spatial sound (will be panned/attenuated based on distance) * user.playSound('explosion', { x: 100, y: 50 }); * ``` */ setListenerPosition(x: number, y: number): void; /** * Configure spatial audio parameters * * @param options - Spatial audio configuration options * * @example * ```typescript * // Configure for a smaller game world * user.configureSpatialAudio({ * maxDistance: 100, // Sounds beyond 100 units are silent * referenceDistance: 10, // Full volume within 10 units * rolloffFactor: 1, // Linear attenuation * panSpread: 0.8 // Strong stereo panning * }); * ``` */ configureSpatialAudio(options: { maxDistance?: number; referenceDistance?: number; rolloffFactor?: number; panSpread?: number; }): void; /** * Get pending audio config commands and clear the queue * * Called by the runtime at the end of each tick to send commands to client. * * @returns Array of audio config commands to send * @internal */ flushAudioConfigCommands(): AudioConfigCommand[]; /** * Check if there are pending audio config commands * * @returns true if there are commands to send * @internal */ hasAudioConfigCommands(): boolean; /** * Get pending sound commands and clear the queue * * Called by the runtime at the end of each tick to send commands to client. * * @returns Array of sound commands to send * @internal */ flushSoundCommands(): Array; /** * Check if there are pending sound commands * * @returns true if there are commands to send * @internal */ hasSoundCommands(): boolean; /** * Request to send all registered sounds to this user * * Call this when you want to transfer audio assets to the client. * Useful for showing a loading screen before sending large audio files. * * The sounds will be sent at the end of the current tick. * * @example * ```typescript * initUser(core: Core, user: User): void { * // Show loading screen * this.showLoadingScreen(user); * * // Request sounds to be sent * user.sendSounds(); * } * * updateUser(core: Core, user: User, deltaTime: number): void { * // Check if sounds are loaded (via client ACK or timeout) * if (user.data.soundsLoaded) { * this.hideLoadingScreen(user); * } * } * ``` */ sendSounds(): void; /** * Check if sounds need to be sent to this user * * @returns true if sendSounds() was called * @internal */ needsSendSounds(): boolean; /** * Clear the pending send sounds flag * * Called by the runtime after sending sounds. * * @internal */ clearSendSounds(): void; /** * Set the audio processor for this user * * Called by ClientRuntime to inject the AudioManager. * This enables unified audio handling in both standalone and connected modes. * * @param processor - The audio processor (typically AudioManager) * @internal */ setAudioProcessor(processor: IAudioProcessor): void; /** * Get the audio processor * * @returns The audio processor, or undefined if not set * @internal */ getAudioProcessor(): IAudioProcessor | undefined; /** * Apply audio commands (play/stop/fadeOut/pause/resume sounds) * * This is the unified entry point for audio playback, used by both: * - ClientRuntime (standalone mode): applies commands from local queue * - NetworkSync (connected mode): applies commands received from server * * In server mode, this method does nothing (audio is played on client only). * * @param commands - Array of sound commands * @internal */ applyAudioCommands(commands: Array): void; /** * Apply audio configuration commands (listener position, spatial config) * * This is the unified entry point for audio configuration, used by both: * - ClientRuntime (standalone mode): applies commands from local queue * - NetworkSync (connected mode): applies commands received from server * * In server mode, this method does nothing (audio is configured on client only). * * @param commands - Array of audio configuration commands * @internal */ applyAudioConfigCommands(commands: AudioConfigCommand[]): void; /** * Trigger a vibration on the user's device * * Vibration requires user interaction to be enabled (browser security). * If using autoplay: false, vibration will work after the first click. * * @param pattern - Duration in ms, or array of [vibrate, pause, vibrate, ...] * * @example * ```typescript * // Simple vibration (100ms) * user.vibrate(100); * * // Pattern: vibrate 100ms, pause 50ms, vibrate 100ms * user.vibrate([100, 50, 100]); * * // Use with game events * if (playerTookDamage) { * user.vibrate([50, 30, 100]); // Impact feedback * } * ``` */ vibrate(pattern: VibrationPattern): void; /** * Cancel any ongoing vibration * * @example * ```typescript * user.cancelVibration(); * ``` */ cancelVibration(): void; /** * Light tap feedback - for button presses, selections */ vibrateTap(): void; /** * Medium tap feedback */ vibrateMediumTap(): void; /** * Heavy tap feedback - for important actions */ vibrateHeavyTap(): void; /** * Success feedback - for completed actions */ vibrateSuccess(): void; /** * Error feedback - for failed actions */ vibrateError(): void; /** * Warning feedback - for important notices */ vibrateWarning(): void; /** * Selection feedback - very light, for UI navigation */ vibrateSelection(): void; /** * Impact feedback (light, medium, or heavy) * * @param intensity - 'light', 'medium', or 'heavy' */ vibrateImpact(intensity?: 'light' | 'medium' | 'heavy'): void; /** * Notification feedback - attention-grabbing pattern */ vibrateNotification(): void; /** * Vibrate a gamepad with dual-motor control * * Modern gamepads have two motors: * - Strong motor (low frequency): Heavy rumble, felt in the palm * - Weak motor (high frequency): Light vibration, felt in the fingers * * @param gamepadIndex - Gamepad index (0-3), or 'all' for all gamepads * @param options - Vibration options (duration, strongMagnitude, weakMagnitude) * * @example * ```typescript * // Strong rumble for 500ms on gamepad 0 * user.vibrateGamepad(0, { * duration: 500, * strongMagnitude: 1.0, * weakMagnitude: 0.0, * }); * * // Light vibration on all gamepads * user.vibrateGamepad('all', { * duration: 200, * strongMagnitude: 0.0, * weakMagnitude: 0.5, * }); * ``` */ vibrateGamepad(gamepadIndex: number | 'all', options: GamepadVibrationOptions): void; /** * Stop vibration on a gamepad * * @param gamepadIndex - Gamepad index (0-3), or 'all' for all gamepads */ stopGamepadVibration(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - light tap */ gamepadTap(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - impact */ gamepadImpact(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - heavy rumble */ gamepadHeavy(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - explosion effect */ gamepadExplosion(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - success feedback */ gamepadSuccess(gamepadIndex?: number | 'all'): void; /** * Quick rumble preset for gamepad - error feedback */ gamepadError(gamepadIndex?: number | 'all'): void; /** * Check if there are pending gamepad vibration commands * * @returns true if there are commands to apply * @internal */ hasGamepadVibrationCommands(): boolean; /** * Flush and return all pending gamepad vibration commands * * @returns Array of gamepad vibration commands * @internal */ flushGamepadVibrationCommands(): GamepadVibrationCommand[]; /** * Set the gamepad vibration processor for this user * * Called by ClientRuntime to inject the GamepadInputs instance. * * @param processor - The gamepad vibration processor * @internal */ setGamepadVibrationProcessor(processor: IGamepadVibrationProcessor): void; /** * Apply gamepad vibration commands * * @param commands - Array of gamepad vibration commands * @internal */ applyGamepadVibrationCommands(commands: GamepadVibrationCommand[]): void; /** * Check if there are pending mobile vibration commands * * @returns true if there are commands to apply * @internal */ hasMobileVibrationCommands(): boolean; /** * Flush and return all pending mobile vibration commands * * @returns Array of mobile vibration commands * @internal */ flushMobileVibrationCommands(): MobileVibrationCommand[]; /** * Set the mobile vibration processor for this user * * Called by ClientRuntime to inject the MobileVibration instance. * * @param processor - The mobile vibration processor * @internal */ setMobileVibrationProcessor(processor: IMobileVibrationProcessor): void; /** * Get the mobile vibration processor * * @returns The mobile vibration processor, or undefined if not set * @internal */ getMobileVibrationProcessor(): IMobileVibrationProcessor | undefined; /** * Apply mobile vibration commands (vibrate/cancel) * * This is the unified entry point for mobile vibration feedback. * Vibrations are client-side only (no network transmission). * * @param commands - Array of mobile vibration commands * @internal */ applyMobileVibrationCommands(commands: MobileVibrationCommand[]): void; /** * Apply vibration orders from an UpdatePacket (BINARY PROTOCOL) * * This method processes decoded vibration orders from the binary UpdatePacket. * Vibration orders are synchronized with the frame for perfect timing. * Supports both mobile vibration (pattern-based) and gamepad vibration (dual-motor). * * Order types: * - MobileVibrate: Trigger a vibration pattern on mobile device * - MobileCancel: Stop any ongoing mobile vibration * - GamepadVibrate: Trigger dual-motor vibration on gamepad * - GamepadCancel: Stop vibration on gamepad * * @param orders - Array of decoded vibration orders from UpdatePacket * @internal */ applyVibrationOrders(orders: AnyVibrationOrder[]): void; /** * Apply audio orders from an UpdatePacket (BINARY PROTOCOL) * * This method processes decoded audio orders from the binary UpdatePacket. * Audio orders use soundId (number) rather than sound names for efficiency. * * Order types: * - PlaySound/PlayGlobalSound: Play a sound * - StopSound: Stop a sound * - FadeOutSound: Fade out and stop * - PauseSound: Pause playback * - ResumeSound: Resume playback * - SetListenerPosition: Set listener for spatial audio * - ConfigureSpatial: Configure spatial audio parameters * * @param orders - Array of decoded audio orders from UpdatePacket * @internal */ applyAudioOrders(orders: AnyAudioOrder[]): void; /** * Handle an audio ACK from the client * * Called by ServerRuntime when receiving 'audio-ack' messages. * Updates internal tracking of the client's audio state. * * @param ack - The audio acknowledgment * @internal */ handleAudioAck(ack: AudioAck): void; /** * Check if a sound is loaded on the client * * @param soundId - The sound ID to check * @returns true if the sound is loaded * * @example * ```typescript * if (user.isSoundLoaded(3)) { * console.log('Sound 3 is ready'); * } * ``` */ isSoundLoaded(soundId: number): boolean; /** * Check if a sound failed to load on the client * * @param soundId - The sound ID to check * @returns Error message if failed, undefined otherwise * * @example * ```typescript * const error = user.getSoundLoadError(3); * if (error) console.warn(error); * ``` */ getSoundLoadError(soundId: number): string | undefined; /** * Get all loaded sounds on the client * * @returns Map of soundId to load info * * @example * ```typescript * const loaded = user.getLoadedSounds(); * console.log(`Loaded: ${loaded.size}`); * ``` */ getLoadedSounds(): ReadonlyMap; /** * Get all sound load errors * * @returns Map of soundId to error info * * @example * ```typescript * const errors = user.getSoundLoadErrors(); * errors.forEach((info, id) => console.warn(id, info.error)); * ``` */ getSoundLoadErrors(): ReadonlyMap; /** * Check if a sound instance is currently playing on the client * * @param instanceId - The instance ID to check * @returns true if the instance is playing * * @example * ```typescript * if (user.isSoundPlaying(musicId)) { * console.log('Music is playing'); * } * ``` */ isSoundPlaying(instanceId: SoundInstanceId): boolean; /** * Get all currently playing sounds on the client * * @returns Map of instanceId to playback info * * @example * ```typescript * const playing = user.getPlayingSounds(); * playing.forEach((info, id) => console.log(id, info.soundId)); * ``` */ getPlayingSounds(): ReadonlyMap; /** * Get the number of sounds currently playing on the client * * @returns Count of active sound instances. * * @example * ```typescript * const count = user.getPlayingSoundCount(); * ``` */ getPlayingSoundCount(): number; /** * Get audio loading state for this client * * Provides a summary of the client's audio loading progress, * useful for showing loading screens or progress bars. * * @param totalExpected - Total number of sounds expected (from SoundRegistry) * @returns Audio loading state * * @example * ```typescript * updateUser(core: Core, user: User): void { * const totalSounds = core.getSoundRegistry().getAll().length; * const audioState = user.getAudioLoadingState(totalSounds); * * if (!audioState.isComplete) { * // Client still loading sounds * console.log(`Loading: ${audioState.loadedCount}/${audioState.totalExpected}`); * } * } * ``` */ getAudioLoadingState(totalExpected: number): { /** Number of sounds successfully loaded on client */ loadedCount: number; /** Total number of sounds expected */ totalExpected: number; /** Number of sounds that failed to load */ errorCount: number; /** Whether all expected sounds are loaded */ isComplete: boolean; /** Names of sounds that failed to load */ errors: string[]; }; /** * Load a macro template * The template will be sent to the client in the next config packet * * @param name - Template name (used for createInstance) * @param template - Macro template definition * @returns The assigned macro ID * * @example * ```typescript * user.loadMacro('button', { * id: 'button', * type: 'ui', * params: { text: 'string', width: 'number' }, * base: [{ fillRect: [0, 0, '$width', 3, ' ', '$fg', '$bg'] }], * states: { normal: { fg: '#FFFFFF', bg: '#333333' } } * }); * ``` */ loadMacro(name: string, template: MacroTemplate): number; /** * Create a macro instance * The instance will be created on the client in the next update packet * * @param config - Instance configuration * @returns The assigned instance ID, or undefined if macro/layer not found * * @example * ```typescript * user.createInstance({ * id: 'btn-play', * macro: 'button', * layer: 'ui', * x: 10, y: 5, * tabIndex: 1, * params: { text: 'Play', width: 8 } * }); * ``` */ createInstance(config: CreateInstanceConfig): number | undefined; /** * Update a macro instance's parameters * * @param name - Instance name * @param params - Parameters to update (merged with existing) * @returns true if instance was found and updated * * @example * ```typescript * user.updateInstance('weather', { intensity: 20 }); * ``` */ updateInstance(name: string, params: Record): boolean; /** * Remove a macro instance * * @param name - Instance name * @returns true if instance was found and removed * * @example * ```typescript * user.removeInstance('btn-play'); * ``` */ removeInstance(name: string): boolean; /** * Register a handler for macro events * * @param instanceName - Instance name to listen to * @param eventType - Event type ('click', 'change', 'submit', 'select') * @param handler - Handler function * * @example * ```typescript * user.onMacroEvent('btn-play', 'click', () => { * console.log('Play button clicked!'); * }); * * user.onMacroEvent('slider-volume', 'change', (value: number) => { * console.log('Volume changed to:', value); * }); * ``` */ onMacroEvent(instanceName: string, eventType: string, handler: (data?: unknown) => void): void; /** * Remove a macro event handler * * @param instanceName - Instance name * @param eventType - Event type to remove (optional, removes all if not specified) * * @example * ```typescript * user.offMacroEvent('btn-play', 'click'); * user.offMacroEvent('btn-play'); // remove all handlers for instance * ``` */ offMacroEvent(instanceName: string, eventType?: string): void; /** * Handle a macro event received from the client * @internal - Called by the server runtime */ handleMacroEvent(event: AnyMacroEvent): void; /** * Get pending macro orders for the next update packet * @internal - Called by UpdatePacketEncoder */ flushMacroOrders(): AnyMacroOrder[]; /** * Check if there are pending macro orders * @internal */ hasPendingMacroOrders(): boolean; /** * Apply macro orders directly (for standalone/local mode) * * This is the local equivalent of receiving macro orders via network. * In local mode, macro orders don't go through encode/decode - they're * applied directly to the MacroEngine. * * @param orders - Array of macro orders to apply * @internal */ applyMacroOrders(orders: AnyMacroOrder[]): void; /** * Get the macro registry (for advanced usage) * @internal */ getMacroRegistry(): MacroRegistry; /** * Get all MacroLoad packets for this user's registered macros * @internal - Called by Core.generateAllLoadPackets() */ getMacroLoads(): MacroLoad[]; /** * Update layer name → ID mappings in the macro registry * @internal */ private updateLayerMappings; /** * Register a layer with a name for macro system * This maps a layer name to its ID for createInstance() * * @param name - Layer name (used in createInstance) * @param layer - The layer object * * @example * ```typescript * user.registerLayerName('ui', uiLayer); * ``` */ registerLayerName(name: string, layer: Layer): void; /** * Load a macro template into the client-side MacroEngine * Called when receiving a LoadType.Macro packet * * @param macroId - The numeric ID of the macro * @param template - The macro template * @internal */ loadMacroTemplate(macroId: number, template: MacroTemplate): void; /** * Update the client-side macro engine * Should be called once per tick (tick-based updates) * * @returns Update result with events and sounds to play * * @example * ```typescript * const result = user.updateMacros(); * user.processMacroEvents(result); * ``` */ updateMacros(): MacroUpdateResult; /** * Process macro events from MacroUpdateResult * Dispatches events to registered handlers (used in standalone mode) * * @param result - The result from updateMacros() * * @example * ```typescript * const result = user.updateMacros(); * user.processMacroEvents(result); * ``` */ processMacroEvents(result: MacroUpdateResult): void; /** * Update mouse state for the macro engine * Converts display-local coordinates to world coordinates using the display's origin * * @param displayX - Mouse X position in display cells (local to display) * @param displayY - Mouse Y position in display cells (local to display) * @param isDown - Whether the mouse button is pressed * * @example * ```typescript * user.updateMacroMouse(mouseX, mouseY, mouseDown); * ``` */ updateMacroMouse(displayX: number, displayY: number, isDown: boolean): void; /** * Get render orders generated by the macro engine, grouped by layer * Orders are converted from world coordinates to display coordinates * by subtracting the display's origin. * * @returns Map of layerId → render orders (in display coordinates) * * @example * ```typescript * const ordersByLayer = user.getMacroRenderOrders(); * const uiOrders = ordersByLayer.get(uiLayerId) ?? []; * ``` */ getMacroRenderOrders(): Map; /** * Get the effect offset (for screen shake, etc.) * * @returns Current offset from effect macros * * @example * ```typescript * const offset = user.getMacroEffectOffset(); * camera.setShake(offset.x, offset.y); * ``` */ getMacroEffectOffset(): { x: number; y: number; }; /** * Get the macro engine (for advanced usage) * @internal */ getMacroEngine(): MacroEngine; /** * Move focus to next focusable macro element * * @example * ```typescript * user.macroFocusNext(); * ``` */ macroFocusNext(): void; /** * Move focus to previous focusable macro element * * @example * ```typescript * user.macroFocusPrevious(); * ``` */ macroFocusPrevious(): void; /** * Activate the currently focused macro element * * @example * ```typescript * user.macroActivateFocused(); * ``` */ macroActivateFocused(): void; /** * Set post-processing configuration for a specific display * * The post-process overlay is rendered ONCE when this is called. * No per-frame cost - only updates when you call this method. * * @param displayId - Display ID (0-255) this setting applies to * @param config - Post-processing configuration (or null to disable) * * @example * ```typescript * // Enable scanlines effect on display 0 * user.setPostProcess(0, { * scanlines: { * enabled: true, * opacity: 0.15, * pattern: 'horizontal' * } * }); * * // Disable all post-processing on display 0 * user.setPostProcess(0, null); * ``` */ private setPostProcess; /** * Enable or disable scanlines effect with default/current settings for a specific display * * @param displayId - Display ID (0-255) * @param enabled - Whether to enable scanlines * * @example * ```typescript * user.setScanlinesEnabled(0, true); // Enable with defaults on display 0 * user.setScanlinesEnabled(0, false); // Disable on display 0 * ``` */ private setScanlinesEnabled; /** * Set scanlines opacity (0-1) for a specific display * * Also enables scanlines if not already enabled. * * @param displayId - Display ID (0-255) * @param opacity - Opacity of dark lines (0 = invisible, 1 = fully opaque) * * @example * ```typescript * user.setScanlinesOpacity(0, 0.2); // Subtle effect on display 0 * user.setScanlinesOpacity(0, 0.5); // Strong effect on display 0 * ``` */ private setScanlinesOpacity; /** * Set scanlines pattern for a specific display * * @param displayId - Display ID (0-255) * @param pattern - Pattern type: 'horizontal', 'vertical', or 'grid' */ private setScanlinesPattern; /** * Enable or configure ambient effect for a specific display * * Ambient effect creates a blurred glow around the terminal canvas, * filling the unused space with colors from the terminal content. * This effect is GPU-accelerated (CSS blur) and has zero CPU cost. * * @param displayId - Display ID (0-255) * @param config - true to enable with defaults, false to disable, * or object with blur and scale settings * * @example * ```typescript * // Enable with defaults (blur: 30px, scale: 1.3) on display 0 * user.setAmbientEffect(0, true); * * // Disable on display 0 * user.setAmbientEffect(0, false); * * // Custom settings on display 1 * user.setAmbientEffect(1, { blur: 50, scale: 1.5 }); * ``` */ private setAmbientEffect; /** * Enable or disable ambient effect for a specific display * * @param displayId - Display ID (0-255) * @param enabled - Whether to enable ambient effect * * @example * ```typescript * user.setAmbientEffectEnabled(0, true); // Enable with defaults on display 0 * user.setAmbientEffectEnabled(0, false); // Disable on display 0 * ``` */ private setAmbientEffectEnabled; /** * Set ambient effect blur intensity for a specific display * * @param displayId - Display ID (0-255) * @param blur - Blur intensity in pixels (default: 30) * * @example * ```typescript * user.setAmbientEffectBlur(0, 50); // More blur on display 0 * user.setAmbientEffectBlur(0, 15); // Less blur on display 0 * ``` */ private setAmbientEffectBlur; /** * Set ambient effect scale factor for a specific display * * @param displayId - Display ID (0-255) * @param scale - Scale factor for the glow (default: 1.3) * * @example * ```typescript * user.setAmbientEffectScale(0, 1.5); // Larger glow area on display 0 * user.setAmbientEffectScale(0, 1.1); // Smaller glow area on display 0 * ``` */ private setAmbientEffectScale; /** * Check if ambient effect is currently enabled */ private isAmbientEffectEnabled; /** * Get current ambient effect configuration */ private getAmbientEffectConfig; /** * Get current post-process configuration */ private getPostProcessConfig; /** * Check if there are pending post-process commands * @internal */ hasPostProcessCommands(): boolean; /** * Flush post-process commands (returns and clears the queue) * @internal */ flushPostProcessCommands(): PostProcessCommand[]; /** Current scaling mode per display (Map) */ private currentScalingModes; /** * Set the pixel-perfect scaling mode for a specific display * * Controls how the terminal canvas is scaled to fit the available space. * Stricter modes produce crisper pixels but may leave empty space. * * @param displayId - Display ID (0-255) this setting applies to * @param mode - Scaling mode: * - `'none'`: Fill space, may have sub-pixel artifacts (default) * - `'responsive'`: Fixed cell size, cols/rows adapt to fill space * - `'eighth'`: Snap to 0.125 increments (1.0, 1.125, 1.25...) * - `'quarter'`: Snap to 0.25 increments (1.0, 1.25, 1.5...) * - `'half'`: Snap to 0.5 increments (1.0, 1.5, 2.0...) * - `'integer'`: Crisp pixels, integer scaling only (1x, 2x, 3x...) * * @example * ```typescript * // For crisp retro-style pixels on display 0 * user.setScalingMode(0, 'integer'); * * // For balanced quality on display 1 * user.setScalingMode(1, 'quarter'); * * // Fill all available space on display 0 * user.setScalingMode(0, 'none'); * ``` */ private setScalingMode; /** * Get current scaling mode for a display * * @param displayId - Display ID (0-255) * @param displayId - Display ID (0-255) * @returns Current scaling mode or null if not set */ private getScalingMode; /** Current cell size configuration per display (Map) */ private currentCellSizes; /** * Set the cell dimensions in pixels for a specific display (for Responsive mode) * * This is only relevant when using `setScalingMode(displayId, 'responsive')`. * In responsive mode, the cell size is fixed and the number of cols/rows * adapts to fill the available space. * * In other scaling modes (integer, quarter, half, none), the cell size * comes from the font/atlas and CSS scaling is applied to fill space. * * @param displayId - Display ID (0-255) this setting applies to * @param width - Cell width in pixels (1-255, default: 8) * @param height - Cell height in pixels (1-255, default: 8) * * @example * ```typescript * // Responsive mode with 8x8 cells on display 0 * user.setScalingMode(0, 'responsive'); * user.setCellSize(0, 8, 8); * * // Responsive mode with larger 16x16 tiles on display 1 * user.setScalingMode(1, 'responsive'); * user.setCellSize(1, 16, 16); * ``` */ private setCellSize; /** * Get current cell size for a display * * @param displayId - Display ID (0-255) * @returns Object with cellWidth and cellHeight in pixels, or default 8x8 if not set */ private getCellSize; /** Current grid configuration per display (Map) */ private currentGridConfigs; /** * Enable or configure the debug grid overlay for a specific display * * The grid shows cell boundaries aligned with the terminal grid. * Useful for debugging layout and alignment issues. * * @param displayId - Display ID (0-255) this setting applies to * @param config - true to enable with defaults, false to disable, * or object with color and lineWidth settings * * @example * ```typescript * // Enable with default red grid on display 0 * user.setGrid(0, true); * * // Disable on display 0 * user.setGrid(0, false); * * // Custom green grid on display 1 * user.setGrid(1, { enabled: true, color: 'rgba(0, 255, 0, 0.5)' }); * * // Custom with thicker lines on display 0 * user.setGrid(0, { enabled: true, color: '#ff0000', lineWidth: 2 }); * ``` */ private setGrid; /** * Enable or disable the grid for a specific display * * @param displayId - Display ID (0-255) * @param enabled - Whether to show the grid * * @example * ```typescript * user.setGridEnabled(0, true); // Show grid on display 0 * user.setGridEnabled(0, false); // Hide grid on display 0 * ``` */ private setGridEnabled; /** * Check if grid is currently enabled for a display * * @param displayId - Display ID (0-255) */ private isGridEnabled; /** * Get current grid configuration for a display * * @param displayId - Display ID (0-255) */ private getGridConfig; /** Currently active palette slot ID per display (Map) */ private currentPaletteSlotIds; /** * Switch to a pre-loaded palette slot for a specific display * * The palette must have been loaded to the Core via `core.loadPaletteToSlot()` first. * This allows instant palette switching without re-sending palette data over the network. * * **Use cases:** * - Day/night cycle transitions * - Different visual themes per game zone * - Accessibility options (high contrast) * - Visual effects (damage flash, power-up) * * @param displayId - Display ID (0-255) this setting applies to * @param slotId - Palette slot ID (0-255) * * @example * ```typescript * // In Core setup - preload palettes * core.loadPaletteToSlot(0, dayPalette); * core.loadPaletteToSlot(1, nightPalette); * core.loadPaletteToSlot(2, dungeonPalette); * * // In IApplication.updateUser() - switch based on game state * if (gameState.isNight) { * user.switchPalette(0, 1); // display 0, night palette * } else { * user.switchPalette(0, 0); // display 0, day palette * } * * // Different palettes for different displays (split-screen) * user.switchPalette(0, dayPalette); // Player 1 display * user.switchPalette(1, nightPalette); // Player 2 display * ``` */ private switchPalette; /** * Get the currently active palette slot ID for a display * * @param displayId - Display ID (0-255) * @returns The active slot ID, or null if no palette has been switched yet */ private getCurrentPaletteSlotId; /** * Send data through the bridge channel to the client application * * The bridge channel allows sending arbitrary JSON data from the server * to the external application (React, HTML) that wraps the UTSP client. * This bypasses the UTSP protocol and is meant for external UI updates. * * **Use cases:** * - Change background image/video behind the terminal * - Update external UI elements (scoreboards, health bars, menus) * - Trigger notifications or modals in the parent application * - Control external music players or ambiance systems * - Synchronize state with React/Vue/Angular components * * **Note:** This should be called from `initUser()` or `updateUser()` in your IApplication. * Messages are queued and sent at `endTick` along with the UpdatePacket for synchronization. * * @param channel - Channel name (e.g., 'background', 'score', 'notification') * @param data - Any JSON-serializable data * * @example * ```typescript * // In your IApplication.updateUser(): * * // Change background when entering a new zone * if (player.zone !== player.previousZone) { * user.sendBridge('background', { * image: `/zones/${player.zone}/bg.jpg`, * music: `/zones/${player.zone}/ambiance.mp3` * }); * } * * // Update external scoreboard * user.sendBridge('score', { value: player.score, combo: player.combo }); * * // Trigger achievement notification * if (player.justUnlockedAchievement) { * user.sendBridge('notification', { * type: 'achievement', * title: 'First Blood!', * icon: '🏆' * }); * } * ``` */ sendBridge(channel: string, data: unknown): void; /** * Get all pending bridge messages and clear the queue * * Called by the runtime at endTick to retrieve messages to send via network. * * @returns Array of bridge messages (channel + data) * @internal */ getBridgeMessages(): Array<{ channel: string; data: unknown; }>; /** * Check if there are pending bridge messages * * @returns true if there are messages to send * @internal */ hasBridgeMessages(): boolean; /** * Returns a debug-friendly snapshot of this user (metadata only) */ getDebugInfo(): { id: string; name: string; bytesSentTotal: number; bytesSentTick: number; availableViewports: Array<{ displayId: number; pixelWidth: number; pixelHeight: number; }>; layers: ReturnType[]; displays: ReturnType[]; displayComposite?: Record; }; } declare class CoreStats { private enabled; private currentStats; /** * Enables or disables statistics collection */ setEnabled(enabled: boolean): void; isEnabled(): boolean; /** Current tick number (0 if no stats) */ get tick(): number; /** Current tick timestamp */ get timestamp(): number; /** Total orders processed this tick */ get totalOrders(): number; /** Orders per layer ID */ get ordersByLayer(): Map; /** Orders per type (0x01, 0x02, etc.) */ get ordersByType(): Map; /** Total number of layers */ get totalLayers(): number; /** Visible layers (intersecting a display) */ get visibleLayers(): number; /** Layers visibles par display ID */ get layersPerDisplay(): Map; /** * Last known composition layer IDs per display (top-to-bottom). * Available only for the latest tick when stats are enabled. */ get displayCompositeLayerIds(): Map; /** Total encoded UpdatePacket size (bytes) */ get updatePacketSize(): number; /** Display header size (bytes) */ get displayHeaderSize(): number; /** Layer header size (bytes) */ get layerHeaderSize(): number; /** Order data size (bytes) */ get orderDataSize(): number; /** Estimated gzip compression ratio (0.25 = 75% compression) */ get compressionRatio(): number; /** Estimated size after gzip compression (bytes) */ get compressedPacketSize(): number; /** Total number of rendered cells */ get totalCells(): number; /** Non-empty cells (char !== ' ') */ get nonEmptyCells(): number; /** Cells with colored background */ get cellsWithBackground(): number; /** Rasterization time (ms) */ get rasterizationTimeMs(): number; /** Encoding time (ms) */ get encodingTimeMs(): number; /** Static/dynamic split information for the last processed user */ get packetSplit(): { userId: string; displayCount: number; staticLayerCount: number; dynamicLayerCount: number; } | undefined; /** Details of all layers processed this tick */ get layerDetails(): Array<{ layerId: number; mustBeReliable: boolean; orderCount: number; updateFlags: number; }> | undefined; /** * Starts collecting statistics for a new tick */ startTick(tickNumber: number): void; /** * Records the exact layer IDs used for composition, in order. */ recordDisplayComposite(displayId: number, layerIdsTopToBottom: number[]): void; /** * Records orders for a layer */ recordLayerOrders(layerId: number, orders: any[]): void; /** * Records the number of layers */ recordLayers(total: number, visible: number): void; /** * Records visible layers per display */ recordDisplayLayers(displayId: number, layerCount: number): void; /** * Records the encoded UpdatePacket size */ recordUpdatePacketSize(totalBytes: number, displayHeaderBytes: number, layerHeaderBytes: number, orderDataBytes: number): void; /** * Records static/dynamic split information */ recordPacketSplit(userId: string, displayCount: number, staticLayerCount: number, dynamicLayerCount: number): void; /** * Records individual layer information */ recordLayerInfo(layerId: number, mustBeReliable: boolean, orderCount: number, updateFlags: number): void; /** * Records rendered cell statistics */ recordRenderStats(totalCells: number, nonEmptyCells: number, cellsWithBg: number): void; /** * Records rasterization time */ recordRasterizationTime(timeMs: number): void; /** * Records encoding time */ recordEncodingTime(timeMs: number): void; /** * Finalizes stats for the current tick * Stats remain available until the next startTick() */ endTick(): void; /** * Resets statistics */ reset(): void; } /** * SoundRegistry - Server-side registry for audio assets * * Stores sound definitions that will be sent to clients. * Supports both File (embedded data) and External (URL reference) modes. * * @example * ```typescript * const registry = new SoundRegistry(); * * // Register a sound with embedded data (File mode) * const coinData = fs.readFileSync('./assets/coin.mp3'); * registry.registerFile('coin', 'mp3', coinData); * * // Register an external sound (FileExternal mode) * registry.registerExternal('music', 'mp3', 'https://cdn.example.com/music.mp3', 1500000); * * // Generate load packets for client * const packets = registry.toLoadPackets(); * ``` */ /** * Internal representation of a registered sound */ interface SoundEntry { /** Unique sound identifier (0-255) */ soundId: number; /** Human-readable name */ name: string; /** Loading mode: 'file' (embedded) or 'external' (URL) */ loadType: SoundLoadType; /** Audio format */ format: SoundFormat; /** Raw audio data (only for 'file' mode) */ data?: Uint8Array; /** URL to fetch from (only for 'external' mode) */ url?: string; /** Expected file size in bytes */ size?: number; /** Checksum for validation */ checksum?: string; } /** * SoundRegistry - Manages sound definitions on the server */ declare class SoundRegistry { private sounds; private nameToId; private nextId; /** * Register a sound with embedded data (File mode) * The audio data will be sent directly to clients via UTSP * * @param name - Human-readable name for the sound * @param format - Audio format (mp3, wav, ogg, etc.) * @param data - Raw audio file data * @returns The assigned sound ID */ registerFile(name: string, format: SoundFormat, data: Uint8Array): number; /** * Register a sound with embedded data using a specific ID * * @param soundId - Specific ID to use (0-255) * @param name - Human-readable name for the sound * @param format - Audio format * @param data - Raw audio file data */ registerFileWithId(soundId: number, name: string, format: SoundFormat, data: Uint8Array): void; /** * Register an external sound (FileExternal mode) * Only the URL is sent to clients, they download from the external source * * @param name - Human-readable name for the sound * @param format - Audio format * @param url - URL where the audio file can be downloaded * @param size - Optional file size in bytes (for progress tracking) * @param checksum - Optional checksum for validation * @returns The assigned sound ID */ registerExternal(name: string, format: SoundFormat, url: string, size?: number, checksum?: string): number; /** * Register an external sound using a specific ID */ registerExternalWithId(soundId: number, name: string, format: SoundFormat, url: string, size?: number, checksum?: string): void; /** * Get a sound entry by ID or name */ get(idOrName: number | string): SoundEntry | undefined; /** * Check if a sound exists */ has(idOrName: number | string): boolean; /** * Get all sound entries */ getAll(): SoundEntry[]; /** * Get all file-mode sounds */ getFileSounds(): SoundEntry[]; /** * Get all external-mode sounds */ getExternalSounds(): SoundEntry[]; /** * Get all sound names */ getNames(): string[]; /** * Get sound ID by name */ getId(name: string): number | undefined; /** * Generate load packets for all registered sounds * Separates File and External sounds into different packets * * @returns Array of load packets ready to send to clients */ toLoadPackets(): Array; /** * Generate a load packet for a specific sound */ toLoadPacket(idOrName: number | string): SoundLoadPacket | SoundExternalLoadPacket | null; /** * Unregister a sound */ unregister(idOrName: number | string): boolean; /** * Clear all registered sounds */ clear(): void; /** * Get the number of registered sounds */ get size(): number; /** * Get registry statistics */ getStats(): { total: number; file: number; external: number; totalFileSize: number; }; private allocateId; private validateId; private checkIdAvailable; } /** * AudioOrderCollector - Converts high-level audio commands to binary AudioOrders * * This class bridges the gap between the User's high-level audio API * (playSound, stopSound, etc.) and the binary AudioOrder protocol. * * It takes SoundRegistry to resolve sound names to IDs, and produces * AudioOrders ready for binary encoding in UpdatePacket. * * @example * ```typescript * const collector = new AudioOrderCollector(soundRegistry); * * // Convert User's pending commands to orders * const audioOrders = collector.collectFromUser(user); * * // audioOrders can now be added to UpdatePacket * ``` */ /** * Collects and converts audio commands to binary AudioOrders */ declare class AudioOrderCollector { private soundRegistry; constructor(soundRegistry: SoundRegistry); /** * Collect all pending audio orders from a user * * This flushes the user's sound and config command queues, * converts them to binary AudioOrders, and returns them. * * @param user - The user to collect orders from * @returns Array of AudioOrders ready for encoding */ collectFromUser(user: User): AnyAudioOrder[]; /** * Convert a sound command to an AudioOrder */ private convertSoundCommand; /** * Convert PlaySoundCommand to PlaySoundOrder or PlayGlobalSoundOrder */ private convertPlayCommand; /** * Convert StopSoundCommand to StopSoundOrder */ private convertStopCommand; /** * Convert FadeOutSoundCommand to FadeOutSoundOrder */ private convertFadeOutCommand; /** * Convert PauseSoundCommand to PauseSoundOrder */ private convertPauseCommand; /** * Convert ResumeSoundCommand to ResumeSoundOrder */ private convertResumeCommand; /** * Convert SetSoundEffectsCommand to SetSoundEffectsOrder */ private convertSetEffectsCommand; /** * Convert AudioConfigCommand to SetListenerPositionOrder or ConfigureSpatialOrder */ private convertConfigCommand; /** * Resolve sound name or ID to a numeric soundId */ private resolveSoundId; /** * Resolve target (instanceId, soundName, or 'all') to targetType and value */ private resolveTarget; /** * Encode volume (0.0-1.0) to byte (0-255) */ private encodeVolume; /** * Encode pitch (0.25x-4.0x) to byte (0-255, 128=1.0x) * Formula: pitch = 0.25 * 2^(byte/64) * Inverse: byte = 64 * log2(pitch / 0.25) */ private encodePitch; /** * Encode fade time (seconds) to byte (1/10 seconds, 0-25.5s) */ private encodeFadeTime; /** * Encode distance for spatial audio (scale: 0-25500 → 0-255) */ private encodeDistance; /** * Encode rolloff factor (0.0-2.55 → 0-255) */ private encodeRolloff; /** * Encode pan spread (0.0-1.0 → 0-255) */ private encodePanSpread; /** * Encode position coordinate for spatial audio (clamp to 0-65535 for Uint16) */ private encodePosition; /** * Encode filter frequency (100-25500 Hz) to byte (1-255, * 100 = Hz) * 0 means disabled */ private encodeFilterFreq; /** * Encode reverb wet mix (0.0-1.0) to byte (0-255) */ private encodeReverb; } /** * VibrationOrderCollector - Converts high-level vibration commands to binary VibrationOrders * * This class bridges the gap between the User's high-level vibration API * (vibrate, vibrateGamepad, etc.) and the binary VibrationOrder protocol. * * It produces VibrationOrders ready for binary encoding in UpdatePacket. * Supports both mobile vibration (pattern-based) and gamepad vibration (dual-motor). * * @example * ```typescript * const collector = new VibrationOrderCollector(); * * // Convert User's pending commands to orders * const vibrationOrders = collector.collectFromUser(user); * * // vibrationOrders can now be added to UpdatePacket * ``` */ /** * Collects and converts vibration commands to binary VibrationOrders */ declare class VibrationOrderCollector { /** * Collect all pending vibration orders from a user * * This flushes the user's vibration command queues (mobile + gamepad), * converts them to binary VibrationOrders, and returns them. * * @param user - The user to collect orders from * @returns Array of VibrationOrders ready for encoding */ collectFromUser(user: User): AnyVibrationOrder[]; /** * Convert a mobile vibration command to a VibrationOrder * Accepts both old format (without target) and new format (with target: 'mobile') */ private convertMobileCommand; /** * Convert MobileVibrateCommand to MobileVibrateOrder * Accepts both old format { pattern, intensity? } and new format { target: 'mobile', pattern, intensity? } */ private convertMobileVibrateCommand; /** * Convert MobileCancelVibrationCommand to MobileCancelOrder */ private convertMobileCancelCommand; /** * Convert a gamepad vibration command to a VibrationOrder * Accepts both old format (without target) and new format (with target: 'gamepad') */ private convertGamepadCommand; /** * Convert GamepadVibrateCommand to GamepadVibrateOrder */ private convertGamepadVibrateCommand; /** * Convert GamepadCancelVibrationCommand to GamepadCancelOrder */ private convertGamepadCancelCommand; } /** * PostProcessOrderCollector - Converts high-level post-process commands to binary PostProcessOrders * * This class bridges the gap between the User's high-level post-process API * (setPostProcess, setAmbientEffect, etc.) and the binary PostProcessOrder protocol. * * It converts PostProcessCommands to PostProcessOrders ready for binary encoding in UpdatePacket. * * @example * ```typescript * const collector = new PostProcessOrderCollector(); * * // Convert User's pending commands to orders * const postProcessOrders = collector.collectFromUser(user); * * // postProcessOrders can now be added to UpdatePacket * ``` */ /** * Collects and converts post-process commands to binary PostProcessOrders */ declare class PostProcessOrderCollector { /** * Collect all pending post-process orders from a user * * This flushes the user's post-process command queue, * converts them to binary PostProcessOrders, and returns them. * * @param user - The user to collect orders from * @returns Array of PostProcessOrders ready for encoding */ collectFromUser(user: User): AnyPostProcessOrder[]; /** * Convert an array of post-process commands to PostProcessOrders * * This is the core conversion logic used by both: * - Server: collectFromUser() → encode → network * - Local: convertCommands() → applyPostProcessOrders() (no encoding) * * @param commands - Array of PostProcessCommands to convert * @returns Array of PostProcessOrders */ convertCommands(commands: PostProcessCommand[]): AnyPostProcessOrder[]; /** * Convert a post-process command to a PostProcessOrder */ private convertCommand; /** * Convert a full set-config command to a SetConfigOrder */ private convertSetConfig; /** * Create a SetScanlinesOrder with given parameters */ private createSetScanlinesOrder; /** * Create a SetAmbientEffectOrder with given parameters */ private createSetAmbientEffectOrder; /** * Convert pattern string to ScanlinesPatternType */ private patternToType; /** * Create a SetScalingModeOrder with given scaling mode */ private createSetScalingModeOrder; /** * Create a SetGridOrder with given grid configuration */ private createSetGridOrder; /** * Create a SwitchPaletteOrder with given slot ID */ private createSwitchPaletteOrder; /** * Create a SetCellSizeOrder with given dimensions */ private createSetCellSizeOrder; /** * Parse CSS color string to RGBA components */ private parseColor; } /** * Engine execution context type */ type CoreMode = 'server' | 'client' | 'standalone'; /** * UTSP engine configuration options */ interface CoreOptions { /** * Execution mode: "server", "client" or "standalone" */ mode?: CoreMode; /** * Maximum number of users allowed (server mode only) * Default: 100 */ maxUsers?: number; } declare class Core { static readonly ANSI_VGA_COLORS: { colorId: number; r: number; g: number; b: number; a: number; e: number; }[]; readonly mode: CoreMode; readonly maxUsers: number; strictMode: boolean; currentTick: number; readonly stats: CoreStats; readonly spriteRegistry: SpriteRegistry; readonly imageFontRegistry: ImageFontRegistry; readonly soundRegistry: SoundRegistry; readonly audioOrderCollector: AudioOrderCollector; readonly vibrationOrderCollector: VibrationOrderCollector; readonly postProcessOrderCollector: PostProcessOrderCollector; readonly encoder: UpdatePacketEncoder; private readonly users; private readonly colorPalette; private readonly paletteSlots; private readonly displayRasterizer; private readonly updatePacketDecoder; private readonly loadDecoder; private cachedLoadPackets; private _renderCallCount; private onPaletteChangedCallback?; private onFontAllocatedCallback?; private onFontBlockAddedCallback?; /** @deprecated Use onFontAllocated/onFontBlockAdded */ private onImageFontChangedCallback?; constructor(options?: CoreOptions); private initializeDefaultPalette; /** * Checks if core is in server mode * * @returns true if server mode * * @example * ```typescript * if (engine.isServer()) { * // Server-specific logic * } * ``` */ isServer(): boolean; /** * Checks if core is in client mode * * @returns true if client mode * * @example * ```typescript * if (engine.isClient()) { * // Client-specific logic * } * ``` */ isClient(): boolean; /** * Checks if core is in standalone mode * * @returns true if standalone mode * * @example * ```typescript * if (engine.isStandalone()) { * // Local preview or tests * } * ``` */ isStandalone(): boolean; /** * Creates a new user in the engine * * @param id - Unique user ID (string) * @param name - User name * @returns Created user * @throws Error if ID already exists * * @example * ```typescript * const engine = new Core(); * const user = engine.createUser("user1", "Alice"); * ``` */ createUser(id: string, name: string): User; /** * Gets a user by ID * * @param id - User ID (string) * @returns The user or null if not found * * @example * ```typescript * const user = engine.getUser("user1"); * if (user) { * console.log(user.name); * } * ``` */ getUser(id: string): User | null; /** * Gets all users from the engine * * @returns Array of all users * * @example * ```typescript * for (const user of engine.getUsers()) { * console.log(`User ${user.id}: ${user.name}`); * } * ``` */ getUsers(): User[]; /** * Checks if a user exists * * @param id - User ID (string) * @returns true if user exists * * @example * ```typescript * if (engine.hasUser("user1")) { * console.log("User exists"); * } * ``` */ hasUser(id: string): boolean; /** * Removes a user from the engine * * @param id - User ID to remove (string) * @returns true if user was removed, false if not found * * @example * ```typescript * const removed = engine.removeUser("user1"); * if (removed) { * console.log("User removed successfully"); * } * ``` */ removeUser(id: string): boolean; /** * Gets total number of users * * @returns Number of users * * @example * ```typescript * console.log(`${engine.getUserCount()} users connected`); * ``` */ getUserCount(): number; /** * Removes all users from the engine * * Useful for complete reset or cleanup before server shutdown * * @example * ```typescript * engine.clearAllUsers(); // All users removed * console.log(engine.getUserCount()); // 0 * ``` */ clearAllUsers(): void; /** * Sets a color in the palette * * 🎨 RESERVED COLORS: * - ColorIDs 240-254: Standard UI palette (CANNOT be modified) * - ColorID 255: Skip/transparent color (CANNOT be modified) * - ColorIDs 0-239: Free for application use (240 colors available) * * @param colorId - Color ID (0-239 only, 240-255 are reserved) * @param r - Red component (0-255) * @param g - Green component (0-255) * @param b - Blue component (0-255) * @param a - Alpha component (0-255, default: 255 = opaque) * @param e - Emission component (0-255, default: 0 = no emission) * @throws Error if colorId is reserved (240-255) or out of bounds * * @example * ```typescript * // ✅ Set a custom color in free range * engine.setColor(0, 255, 0, 0, 255, 0); * * // ✅ Set a semi-transparent color * engine.setColor(1, 0, 255, 0, 128, 0); * * // ✅ Set an emissive color (bloom/glow) * engine.setColor(2, 0, 255, 255, 255, 255); * * // ❌ ERROR: ColorIDs 240-254 are reserved (UI palette) * // engine.setColor(245, 255, 0, 0); // Throws error * * // ❌ ERROR: ColorID 255 is reserved (skip color) * // engine.setColor(255, 255, 255, 255); // Throws error * ``` */ setColor(colorId: number, r: number, g: number, b: number, a?: number, e?: number): void; /** * Register a callback to be notified when palette changes * * Use this to update the renderer when palette is modified via setColor() or resetPalette(). * * @param callback - Function called with the new palette * * @example * ```typescript * core.onPaletteChanged((palette) => { * const paletteArray = Array.from(palette.values()); * renderer.setPalette(paletteArray); * }); * ``` */ onPaletteChanged(callback: (palette: Map) => void): void; /** * Gets a color from the palette * * @param colorId - Color ID (0-255) * @returns RGBA+E color or null if ID doesn't exist * * @example * ```typescript * const color = engine.getColor(16); * if (color) { * console.log(`RGB: ${color.r}, ${color.g}, ${color.b}`); * console.log(`Emission: ${color.e}`); * } * ``` */ getColor(colorId: number): { r: number; g: number; b: number; a: number; e: number; } | null; /** * Gets the entire color palette * * @returns Map of colors by ID * * @example * ```typescript * const palette = engine.getPalette(); * palette.forEach((color, id) => { * console.log(`Color ${id}: rgba(${color.r}, ${color.g}, ${color.b}, ${color.a}) emission: ${color.e}`); * }); * ``` */ getPalette(): Map; /** * Resets palette to default VGA colors * * @example * ```typescript * engine.resetPalette(); // Back to VGA 16 colors palette * ``` */ resetPalette(): void; /** * Load a palette into a slot for later switching * * Palettes are stored in the Core and can be switched per-display at runtime. * This allows preloading multiple palettes (e.g., day/night themes) * and switching between them without re-sending the palette data. * * @param slotId - Slot ID (0-255) * @param colors - Array of colors to load * * @example * ```typescript * // Preload day and night palettes * core.loadPaletteToSlot(0, dayColors); * core.loadPaletteToSlot(1, nightColors); * * // Later, switch display to night palette * const display = user.getDisplays()[0]; * display.switchPalette(1); * ``` */ loadPaletteToSlot(slotId: number, colors: Array<{ colorId: number; r: number; g: number; b: number; a?: number; e?: number; }>): void; /** * Get a palette from a slot * * @param slotId - Slot ID (0-255) * @returns The palette map, or null if slot is empty * * @example * ```typescript * const nightPalette = core.getPaletteFromSlot(1); * if (nightPalette) { * console.log(`Night palette has ${nightPalette.size} colors`); * } * ``` */ getPaletteFromSlot(slotId: number): Map | null; /** * Check if a palette slot is loaded * * @param slotId - Slot ID (0-255) * @returns true if the slot has a palette loaded * * @example * ```typescript * if (core.hasPaletteSlot(1)) { * console.log('Slot 1 is ready'); * } * ``` */ hasPaletteSlot(slotId: number): boolean; /** * Clear a palette slot * * @param slotId - Slot ID (0-255) * * @example * ```typescript * core.clearPaletteSlot(1); * ``` */ clearPaletteSlot(slotId: number): void; /** * Clear all palette slots * * @example * ```typescript * core.clearAllPaletteSlots(); * ``` */ clearAllPaletteSlots(): void; /** * Converts a color ID to a CSS rgba() string * * Useful for HTML/Canvas rendering * * @param colorId - Color ID (0-255) * @returns CSS string "rgba(r, g, b, a)" or null if ID doesn't exist * * @example * ```typescript * const cssColor = engine.getColorCSS(16); * if (cssColor) { * ctx.fillStyle = cssColor; // "rgba(255, 0, 0, 1)" * } * ``` */ getColorCSS(colorId: number): string | null; /** * Gets all user IDs * * @returns Array of IDs (strings) * * @example * ```typescript * const ids = engine.getUserIds(); // ["user1", "user2", ...] * ``` */ getUserIds(): string[]; /** * Iterates over all users with a callback * * @param callback - Function called for each user * * @example * ```typescript * engine.forEachUser((user) => { * user.displays.forEach(d => d.clear()); * }); * ``` */ forEachUser(callback: (user: User) => void): void; /** * Filters users according to a predicate * * @param predicate - Filter function * @returns Array of matching users * * @example * ```typescript * const activeUsers = engine.filterUsers(user => user.displays.length > 0); * ``` */ filterUsers(predicate: (user: User) => boolean): User[]; /** * Finds a user according to a predicate * * @param predicate - Search function * @returns First matching user or undefined * * @example * ```typescript * const alice = engine.findUser(user => user.name === "Alice"); * ``` */ findUser(predicate: (user: User) => boolean): User | undefined; /** * Gets current tick number * * @returns Current tick number * * @example * ```typescript * console.log(`Current tick: ${engine.getCurrentTick()}`); * ``` */ getCurrentTick(): number; /** * Gets CoreStats object to access performance statistics * * @returns The engine's CoreStats instance * * @example * ```typescript * // Enable stats * engine.getStats().setEnabled(true); * * // After a few ticks... * const report = engine.getStats().generateReport(10); * console.log(report); * * // Access averages * const avg = engine.getStats().getAverageStats(100); * console.log(`Avg packet size: ${avg.avgPacketSize} bytes`); * ``` */ getStats(): CoreStats; /** * Ends current tick and generates update packets for all users * * This method: * 1. Encodes displays and layers of each user into binary packets * 2. Only sends layers that have called commit() (optimization) * 3. Separates static and dynamic layers for network optimization * 4. Increments tick counter * 5. Returns a Map with split packets ready to send * * Network strategy: * - Reliable Layers → Reliable channel (guaranteed delivery) * - Unreliable Layers → Volatile channel (can drop packets for performance) * - Audio/Vibration/Macro/PostProcess orders → Only in dynamic packet * * @returns Map * * @example * ```typescript * const packets = engine.endTick(); * packets.forEach(({ static: staticPacket, dynamic: dynamicPacket }, userId) => { * if (staticPacket) { * network.sendToClient(userId, 'update-static', staticPacket); // Reliable * } * if (dynamicPacket) { * network.sendToClientVolatile(userId, 'update-dynamic', dynamicPacket); // Volatile * } * }); * ``` */ endTick(): Map; /** * Generates a complete snapshot of a user's state * * Unlike endTick() which generates incremental updates * (dynamic layers + static layers not yet sent), getSnapshot() * encodes ALL layers (static + dynamic), ignoring the * hasSentStatic flag. * * The snapshot represents the complete state at time T, while * updates are cumulative deltas. * * Use cases: * - Initial connection: Send complete state to new client * - Reconnection: Resynchronize client after disconnection * - Spectators: Allow observer to join mid-game * - Save: Capture state for persistence * - Migration: Transfer user to another server * - Replay: Record initial state for replay * * @param userId - User ID * @returns Complete binary packet (Uint8Array) or null if user doesn't exist * * @example * ```typescript * // Initial connection * socket.on('connect', (userId) => { * const snapshot = engine.getSnapshot(userId); * socket.emit('initial-state', snapshot); * }); * * // Reconnection after disconnect * socket.on('reconnect', (userId) => { * const snapshot = engine.getSnapshot(userId); * socket.emit('resync-state', snapshot); * }); * * // Spectator joining during game * socket.on('spectate', (spectatorId, targetUserId) => { * const snapshot = engine.getSnapshot(targetUserId); * socket.to(spectatorId).emit('spectate-state', snapshot); * }); * ``` */ getSnapshot(userId: string): Uint8Array | null; /** * Resets tick counter to 0 * * Useful for complete reset or starting a new session * * @example * ```typescript * engine.resetTick(); * console.log(engine.getCurrentTick()); // 0 * ``` */ resetTick(): void; /** * Applies a decoded UpdatePacket to a user (CLIENT-SIDE RECONSTRUCTION) * * This method is the main entry point for reconstructing a user's state * on the client side from a packet received from the server. * * It performs: * 1. User validation (must exist) * 2. Update application via user.applyUpdate() * 3. Automatic rasterization in client mode * * IMPORTANT: This method should ONLY be called in CLIENT mode. * The server generates packets via endTick(), the client applies them. * * @param userId - Target user ID * @param packet - Decoded UpdatePacket (via UpdatePacketDecoder) * @returns true if update was applied, false if user doesn't exist * * @example * ```typescript * // Client side (ClientRuntime) * import { UpdatePacketDecoder } from '@utsp/core'; * * const decoder = new UpdatePacketDecoder(); * * websocket.on('update', (buffer: Uint8Array) => { * // Decode binary packet * const packet = decoder.decode(buffer); * * // Apply to local user * const applied = core.applyUpdatePacket('user1', packet); * * if (applied) { * // Rendering is automatically updated via getRenderState() * console.log(`Update tick ${packet.tick} applied`); * } * }); * ``` */ applyUpdatePacket(userId: string, packet: UpdatePacket): boolean; /** * Applies a raw UpdatePacket buffer (decodes then applies) * * Convenient version that combines decoding + application in one step. * * @param userId - Target user ID * @param buffer - Binary buffer received from server * @returns true if update was applied, false if user doesn't exist * * @example * ```typescript * // Simplified version for runtime * websocket.on('update', (buffer: Uint8Array) => { * const packet = core.applyUpdatePacketBuffer('user1', buffer); * if (packet) { * // Handle post-process orders, etc. * } * }); * ``` */ applyUpdatePacketBuffer(userId: string, buffer: Uint8Array): UpdatePacket | null; /** * Applies a LoadPacket received from the server (CLIENT-SIDE) * * This method automatically decodes the binary buffer and applies * the asset to the Core according to its LoadType: * - ColorPalette → loadPaletteToSlot() * - Sprite → loadUnicolorSprites() * - MulticolorSprite → loadMulticolorSprites() * - Sound → (TODO: implement audio system) * * @param buffer - Encoded binary buffer received via WebSocket * @returns true if applied successfully, false on error * * @example * ```typescript * // Client side (ClientRuntime) * websocket.on('load', (buffer: Uint8Array) => { * core.applyLoadPacket(buffer); * }); * ``` */ applyLoadPacket(buffer: Uint8Array): boolean; /** * Generates a LoadPacket for a specific palette slot (SERVER-SIDE) * * Encodes a palette slot in binary format ready to send to clients. * The client will store this in its own paletteSlots Map for later switching. * * @param slotId - The slot ID (0-255) * @returns Encoded binary buffer (or null if slot doesn't exist) * * @example * ```typescript * // Server side - send specific palette slot * const slotPacket = core.generatePaletteSlotLoadPacket(1); * if (slotPacket) { * websocket.emit('load', slotPacket); * } * ``` */ generatePaletteSlotLoadPacket(slotId: number): Uint8Array | null; /** * Generates LoadPackets for all palette slots (SERVER-SIDE) * * @returns Array of encoded binary buffers for all non-empty slots * * @example * ```typescript * // Server side - send all palette slots to client * const slotPackets = core.generateAllPaletteSlotLoadPackets(); * slotPackets.forEach(packet => { * websocket.emit('load', packet); * }); * ``` */ generateAllPaletteSlotLoadPackets(): Uint8Array[]; /** * Generates a LoadPacket for all unicolor sprites (SERVER-SIDE) * * @returns Encoded binary buffer (or null if no sprites) * * @example * ```typescript * const spritesPacket = core.generateUnicolorSpritesLoadPacket(); * if (spritesPacket) { * websocket.emit('load', spritesPacket); * } * ``` */ generateUnicolorSpritesLoadPacket(): Uint8Array | null; /** * Generates a LoadPacket for all multicolor sprites (SERVER-SIDE) * * @returns Encoded binary buffer (or null if no sprites) * * @example * ```typescript * const spritesPacket = core.generateMulticolorSpritesLoadPacket(); * if (spritesPacket) { * websocket.emit('load', spritesPacket); * } * ``` */ generateMulticolorSpritesLoadPacket(): Uint8Array | null; /** * Generates ALL LoadPackets (palette slots + sprites + fonts) (SERVER-SIDE) * * Useful when a client initially connects to send them * all assets at once. * * @returns Array of all encoded buffers ready to send * * @example * ```typescript * // When a new client connects * socket.on('join', (userId) => { * const packets = core.generateAllLoadPackets(); * packets.forEach(packet => { * socket.to(userId).emit('load', packet); * }); * }); * ``` */ generateAllLoadPackets(): Uint8Array[]; /** * Generates MacroLoad packets for a specific user's registered macros * * @param userId - Target user ID * @returns Array of encoded MacroLoad packets */ generateMacroLoadPackets(userId: string): Uint8Array[]; /** * Generates an initial update packet for a specific user (SERVER-SIDE) * * This is used to send any pending PostProcessCommands (like switchPalette) * that were created during initUser() but before the first regular tick. * * @param userId - Target user ID * @returns Encoded update packet, or null if no pending commands * * @example * ```typescript * // Server side - after initUser and load packets * const initialPacket = core.generateInitialUpdatePacket(clientId); * if (initialPacket) { * socket.emit('update', initialPacket); * } * ``` */ generateInitialUpdatePacket(userId: string): Uint8Array | null; /** * Applies Input Bindings to a user (CLIENT-SIDE) * * This method allows the client to configure input bindings * that it must capture and send back to the server in compressed form. * * @param userId - Target user ID * @param json - JSON string of bindings from user.getInputBindingsLoadPacket() * @returns true if applied successfully, false if user doesn't exist * * @example * ```typescript * // Client side * websocket.on('input-bindings', (json: string) => { * core.applyInputBindingsLoadPacket('user1', json); * }); * ``` */ applyInputBindingsLoadPacket(userId: string, json: string): boolean; /** * Generates render state for a user * * This method converts a user's displays and orders into a readable * format with CSS colors, useful for: * - Render debugging * - Server-side preview * - Automated tests * - Snapshot generation * * @param userId - User ID * @returns Render state or null if user doesn't exist * * @example * ```typescript * const state = engine.getRenderState("user1"); * if (state) { * console.log(`Tick: ${state.tick}`); * for (const display of state.displays) { * console.log(`Display ${display.width}x${display.height}`); * // Display cells * for (let y = 0; y < display.height; y++) { * let line = ""; * for (let x = 0; x < display.width; x++) { * const cell = display.cells[y * display.width + x]; * line += cell.char; * } * console.log(line); * } * } * } * ``` */ getRenderState(userId: string, layerFilter?: (layer: Layer) => boolean): UserRenderState | null; /** * Generates render state for all users * * @returns Map of render states by userId * * @example * ```typescript * const allStates = engine.getAllRenderStates(); * allStates.forEach((state, userId) => { * console.log(`User ${userId}: ${state.displays.length} displays`); * }); * ``` */ getAllRenderStates(): Map; /** * Enables or disables statistics collection * * @param enabled - true to enable, false to disable * * @example * ```typescript * engine.enableStats(true); * // ... run game loop ... * const report = engine.getStatsReport(); * console.log(report); * ``` */ enableStats(enabled: boolean): void; /** * Returns the statistics instance * * @returns The CoreStats object */ getStatsInstance(): CoreStats; /** * Resets statistics for the current tick * * @example * ```typescript * engine.resetStats(); * ``` */ resetStats(): void; /** * Loads unicolor sprites into the registry. * * Unicolor sprites store only character codes. Colors (fg/bg) are * defined at render time via SpriteOrder. * * ✨ Supports strings for data (including row-by-row arrays) and auto-converts * characters to CP437 automatically. * * @param definitions - Array of sprite definitions * * @example * ```typescript * // Load a 4x4 unicolor sprite using a flat string * engine.loadUnicolorSprites([ * { * spriteId: 1, * sizeX: 4, * sizeY: 4, * data: "##### ## #####" * } * ]); * * // Load using a string array (row by row) - cleaner! * engine.loadUnicolorSprites([ * { * spriteId: 2, * sizeX: 4, * sizeY: 4, * data: [ * "####", * "# #", * "# #", * "####" * ] * } * ]); * ``` */ loadUnicolorSprites(definitions: UnicolorSpriteDefinition[]): void; /** * Loads multicolor sprites into the registry. * * Multicolor sprites store charCode + fgColorId + bgColorId per cell. * Optimized for sprites with lots of visual detail. * * ✨ Supports string characters for charCode and auto-converts to CP437. * * @param definitions - Array of sprite definitions * * @example * ```typescript * // Load a 2x2 multicolor sprite * engine.loadMulticolorSprites([ * { * spriteId: 10, * sizeX: 2, * sizeY: 2, * data: [ * { charCode: 'A', fgColorId: 12, bgColorId: 0 }, * { charCode: 'B', fgColorId: 14, bgColorId: 0 }, * { charCode: 'C', fgColorId: 14, bgColorId: 0 }, * { charCode: 'D', fgColorId: 12, bgColorId: 0 } * ] * } * ]); * ``` */ loadMulticolorSprites(definitions: MulticolorSpriteDefinition[]): void; /** * Load Font structure (allocate font) * Defines the font dimensions and allocates the atlas in memory (empty) * * @param glyphWidth Width of each glyph source in pixels * @param glyphHeight Height of each glyph source in pixels * @param atlasBlocks Number of 256-char blocks (1, 4, or 16) * @param cellWidth Target rendering width * @param cellHeight Target rendering height */ loadFont(glyphWidth: number, glyphHeight: number, atlasBlocks: AtlasBlocks, cellWidth: number, cellHeight: number): void; /** * Load Font data block * Uploads a PNG chunk (256 chars) to the previously allocated font atlas * * @param blockIndex Index of the block (0-15) * @param pathOrData Path to the PNG file (string) or raw data (Uint8Array) */ loadFontBlock(blockIndex: number, path: string): Promise; loadFontBlock(blockIndex: number, data: Uint8Array): void; /** * Unloads a unicolor sprite from memory * * @param spriteId - ID of the sprite to unload * @returns true if sprite was found and removed, false otherwise * * @example * ```typescript * const removed = engine.unloadUnicolorSprite(1); * if (removed) { * console.log("Sprite 1 unloaded"); * } * ``` */ unloadUnicolorSprite(spriteId: number): boolean; /** * Unloads a multicolor sprite from memory * * @param spriteId - ID of the sprite to unload * @returns true if sprite was found and removed, false otherwise * * @example * ```typescript * const removed = engine.unloadMulticolorSprite(10); * if (removed) { * console.log("Sprite 10 unloaded"); * } * ``` */ unloadMulticolorSprite(spriteId: number): boolean; /** * Clears all unicolor sprites from memory * * @example * ```typescript * engine.clearUnicolorSprites(); * console.log("All unicolor sprites have been unloaded"); * ``` */ clearUnicolorSprites(): void; /** * Clears all multicolor sprites from memory * * @example * ```typescript * engine.clearMulticolorSprites(); * console.log("All multicolor sprites have been unloaded"); * ``` */ clearMulticolorSprites(): void; /** * Clears all sprites (unicolor and multicolor) * * @example * ```typescript * engine.clearAllSprites(); * console.log("All sprites have been unloaded"); * ``` */ clearAllSprites(): void; /** * Counts the number of loaded unicolor sprites * * @returns Number of unicolor sprites in memory * * @example * ```typescript * const count = engine.getUnicolorSpriteCount(); * console.log(`${count} unicolor sprites loaded`); * ``` */ getUnicolorSpriteCount(): number; /** * Counts the number of loaded multicolor sprites * * @returns Number of multicolor sprites in memory * * @example * ```typescript * const count = engine.getMulticolorSpriteCount(); * console.log(`${count} multicolor sprites loaded`); * ``` */ getMulticolorSpriteCount(): number; /** * Counts total number of loaded sprites (unicolor + multicolor) * * @returns Total number of sprites in memory * * @example * ```typescript * const total = engine.getTotalSpriteCount(); * console.log(`${total} sprites total`); * ``` */ getTotalSpriteCount(): number; /** * Checks if a unicolor sprite exists * * @param spriteId - ID of the sprite to check * @returns true if sprite exists, false otherwise * * @example * ```typescript * if (engine.hasUnicolorSprite(1)) { * console.log("Sprite 1 available"); * } * ``` */ hasUnicolorSprite(spriteId: number): boolean; /** * Checks if a multicolor sprite exists * * @param spriteId - ID of the sprite to check * @returns true if sprite exists, false otherwise * * @example * ```typescript * if (engine.hasMulticolorSprite(10)) { * console.log("Sprite 10 available"); * } * ``` */ hasMulticolorSprite(spriteId: number): boolean; /** * Gets the sprite registry (for rasterizer only) * @internal */ getSpriteRegistry(): SpriteRegistry; /** * Gets the sound registry * * Use this to access the full SoundRegistry API for advanced operations. * * @returns The SoundRegistry instance * * @example * ```typescript * const registry = core.getSoundRegistry(); * console.log(`${registry.size} sounds registered`); * ``` */ getSoundRegistry(): SoundRegistry; /** * Loads and registers a sound from a file path * * Works in both Node.js and browser environments: * - Node.js: loads synchronously with fs.readFileSync * - Browser: loads asynchronously with fetch * * Format is auto-detected from file extension. * * @param name - Human-readable name (e.g., 'coin', 'explosion') * @param path - Path to the audio file (relative or absolute) * @returns Promise resolving to the assigned sound ID (0-255) * * @example * ```typescript * // In init() - same code works on server and client * async init(core: Core): Promise { * await core.loadSound('coin', './sounds/coin.mp3'); * await core.loadSound('jump', './sounds/jump.wav'); * } * ``` */ loadSound(name: string, path: string): Promise; /** * Loads multiple sounds at once * * @param sounds - Object mapping sound names to file paths * @returns Promise resolving to object mapping names to IDs * * @example * ```typescript * async init(core: Core): Promise { * await core.loadSounds({ * coin: './sounds/coin.mp3', * jump: './sounds/jump.wav', * hit: './sounds/hit.ogg', * }); * } * ``` */ loadSounds(sounds: Record): Promise>; /** * Detects audio format from file extension */ private detectSoundFormat; /** * Loads resource data from path (isomorphic: Node.js or browser) */ private readResource; /** * Registers a sound with embedded data (File mode) - Low-level API * * The audio data will be sent to clients via UTSP protocol. * Use this for small sounds that should be guaranteed to arrive. * * @param name - Human-readable name (e.g., 'coin', 'explosion') * @param format - Audio format ('mp3', 'wav', 'ogg', etc.) * @param data - Raw audio file data as Uint8Array or Buffer * @returns The assigned sound ID (0-255) * * @example * ```typescript * // If you already have the data * core.registerSound('coin', 'mp3', myAudioData); * ``` */ registerSound(name: string, format: SoundFormat, data: Uint8Array): number; /** * Registers an external sound (FileExternal mode) * * Only the URL is sent to clients, who download from the external source. * Use this for large sounds (music) to reduce UTSP bandwidth. * * @param name - Human-readable name (e.g., 'background-music') * @param format - Audio format ('mp3', 'wav', 'ogg', etc.) * @param url - URL where clients can download the audio * @param size - Optional file size in bytes (for progress tracking) * @returns The assigned sound ID (0-255) * * @example * ```typescript * const soundId = core.registerExternalSound( * 'background-music', * 'mp3', * 'https://cdn.example.com/music/theme.mp3', * 1_500_000 // 1.5 MB * ); * ``` */ registerExternalSound(name: string, format: SoundFormat, url: string, size?: number): number; /** * Checks if a sound is registered * * @param nameOrId - Sound name (string) or ID (number) * @returns true if sound exists * * @example * ```typescript * if (core.hasSound('coin')) { * console.log('Coin sound is ready'); * } * ``` */ hasSound(nameOrId: string | number): boolean; /** * Generates LoadPackets for all registered sounds * * Returns separate packets for File and External sounds. * Send these to clients during initial connection. * * @returns Array of sound load packets * * @example * ```typescript * const soundPackets = core.generateSoundLoadPackets(); * soundPackets.forEach(packet => { * socket.emit('sound-load', packet); * }); * ``` */ generateSoundLoadPackets(): Array<_utsp_types.SoundLoadPacket | _utsp_types.SoundExternalLoadPacket>; /** * Loads an image font from a PNG file path * * Works identically on server (Node.js) and client (browser). * The PNG is loaded and registered with an auto-assigned ID. * * @param name - Human-readable name for the font (e.g., 'tileset', 'icons') * @param path - Path to the PNG atlas file * @param options - Font configuration options * @returns Promise resolving to the assigned font ID (0-255) * * @example * ```typescript * // In init() - same code works on server and client * async init(core: Core): Promise { * await core.loadImageFont('tileset', './fonts/tileset.png', { * glyphWidth: 16, * glyphHeight: 16, * atlasBlocks: 4, // 1024 chars * }); * } * * // Later, use by name or ID * const id = core.getImageFontId('tileset'); * ``` */ loadImageFont(name: string, path: string, options: ImageFontOptions): Promise; /** * Loads multiple image fonts at once * * @param fonts - Object mapping font names to { path, options } * @returns Promise resolving to object mapping names to IDs * * @example * ```typescript * async init(core: Core): Promise { * await core.loadImageFonts({ * tileset: { * path: './fonts/tileset.png', * options: { glyphWidth: 16, glyphHeight: 16, atlasBlocks: 4 } * }, * icons: { * path: './fonts/icons.png', * options: { glyphWidth: 8, glyphHeight: 8, atlasBlocks: 1 } * } * }); * } * ``` */ loadImageFonts(fonts: Record): Promise>; /** * Loads image data from path (isomorphic: Node.js or browser) */ private loadImageData; /** * Gets the font ID for a named image font * * @param name - Font name * @returns Font ID or undefined if not found * * @example * ```typescript * const id = core.getImageFontId('tileset'); * if (id !== undefined) { * console.log(`Tileset font ID: ${id}`); * } * ``` */ getImageFontId(name: string): number | undefined; /** * Gets an image font by its name * * @param name - Font name * @returns The ImageFont instance or null if not found * * @example * ```typescript * const font = core.getImageFontByName('tileset'); * if (font) { * console.log(`Max charCode: ${font.getMaxCharCode()}`); * } * ``` */ getImageFontByName(name: string): ImageFont | null; /** * Registers a callback for image font changes * * Called when an image font is loaded. * * @param callback - Function called with the fontId /** * Register a callback for when a font structure is allocated * (Does not imply data is ready, just dimensions) */ onFontAllocated(callback: () => void): void; /** * Register a callback for when a font data block is added */ onFontBlockAdded(callback: (blockIndex: number) => void): void; /** * Register a callback for when an image font is changed (loaded or updated) * DEPRECATED: Use onFontAllocated/onFontBlockAdded for granular updates * * @param callback Function to call when an image font changes * * @example * ```typescript * core.onImageFontChanged((fontId) => { * // Legacy support * }); * ``` */ onImageFontChanged(callback: (fontId: number) => void): void; /** * Gets the image font registry (low-level API) * * @returns The ImageFontRegistry instance * * @example * ```typescript * const registry = engine.getImageFontRegistry(); * const font = registry.getFont(1); * ``` */ getImageFontRegistry(): ImageFontRegistry; /** * Loads an image font (PNG atlas) by ID (low-level API) * * Prefer using loadImageFont() with a name for simpler usage. * * @param fontId - Unique font ID (0-255) * @param config - Image font configuration * @throws Error if fontId is already in use * * @example * ```typescript * engine.loadImageFontById(1, { * glyphWidth: 16, * glyphHeight: 16, * atlasBlocks: 4, * imageData: pngBuffer * }); * ``` */ loadImageFontById(fontId: number, config: ImageFontConfig): void; /** * Gets an image font by its ID * * @param fontId - Font ID (0-255) * @returns The ImageFont instance or null if not found * * @example * ```typescript * const font = engine.getImageFont(1); * if (font) { * console.log(`Atlas blocks: ${font.getAtlasBlocks()}`); * console.log(`Max charCode: ${font.getMaxCharCode()}`); * } * ``` */ getImageFont(fontId: number): ImageFont | null; /** * Checks if an image font exists by ID * * @param fontId - Font ID (0-255) * @returns true if font exists * * @example * ```typescript * if (engine.hasImageFont(1)) { * console.log("ImageFont 1 is loaded"); * } * ``` */ hasImageFont(fontId: number): boolean; /** * Checks if an image font exists by name * * @param name - Font name * @returns true if font exists * * @example * ```typescript * if (engine.hasImageFontByName('tileset')) { * console.log("Tileset font is loaded"); * } * ``` */ hasImageFontByName(name: string): boolean; /** * Unloads an image font from the registry by ID * * @param fontId - Font ID to unload (0-255) * @returns true if font was unloaded * * @example * ```typescript * const removed = engine.unloadImageFont(1); * if (removed) { * console.log("ImageFont 1 unloaded"); * } * ``` */ unloadImageFont(fontId: number): boolean; /** * Unloads an image font from the registry by name * * @param name - Font name to unload * @returns true if font was unloaded * * @example * ```typescript * const removed = engine.unloadImageFontByName('tileset'); * if (removed) { * console.log("Tileset font unloaded"); * } * ``` */ unloadImageFontByName(name: string): boolean; /** * Clears all image fonts * * @example * ```typescript * engine.clearImageFonts(); * console.log("All image fonts cleared"); * ``` */ clearImageFonts(): void; /** * Counts the number of loaded image fonts * * @returns Number of image fonts in memory * * @example * ```typescript * const count = engine.getImageFontCount(); * console.log(`${count} image fonts loaded`); * ``` */ getImageFontCount(): number; /** * Gets all loaded image font IDs * * @returns Array of fontIds (0-255) * * @example * ```typescript * const fontIds = engine.getImageFontIds(); * console.log(`Loaded image fonts: ${fontIds.join(", ")}`); * ``` */ getImageFontIds(): number[]; /** * Gets all loaded image font names * * @returns Array of font names * * @example * ```typescript * const names = engine.getImageFontNames(); * console.log(`Loaded image fonts: ${names.join(", ")}`); * ``` */ getImageFontNames(): string[]; } /** * Layer configuration options. */ interface LayerOptions { /** Optional layer name for tooling/debug. */ name?: string; /** * CharCode mode for this layer (immutable after creation) * - '8bit': 256 char codes (CP437 default) * - '16bit': 65536 char codes (256 atlas blocks × 256 chars) * @default '8bit' */ charCodeMode?: CharCodeMode; /** * If true, the layer is sent on the reliable channel. * @default false */ mustBeReliable?: boolean; /** * If true, the layer is considered a macro layer (ephemeral, local effects). * @default false */ isMacroLayer?: boolean; } /** * Represents a renderable layer in world space. * * Layers store rendering orders and metadata (position, z-order, size) * and can be composed by displays at the `User` level. */ declare class Layer { private id; private name?; private isMacroLayer; private origin; private orders; private zOrder; private data; private width; private height; private mustBeReliable; private spriteRegistry?; private mode; private charCodeMode; private previousOrigin; private previousZOrder; private enabled; private useSetMode; private needsCommit; private static rasterizer; /** * Creates a new layer. * * @param origin - Layer origin in world space. * @param zOrder - Z-order for stacking (higher = on top). * @param width - Layer width in cells (1-256). * @param height - Layer height in cells (1-256). * @param options - Layer options. * * Options: * - `name`: Optional name for tooling/debug. * - `charCodeMode`: `'8bit'` (default) or `'16bit'`. * - `mustBeReliable`: `true` to use reliable channel, `false` for volatile. * - `isMacroLayer`: `true` for macro effects, `false` for standard layers. * * @example * // 8-bit layer (default) - CP437 (block 0) only * const gameLayer = new Layer(new Vector2(0, 0), 0, 80, 25); * * @example * // 16-bit layer - atlas blocks beyond CP437 * const atlasLayer = new Layer(new Vector2(0, 0), 10, 40, 10, { charCodeMode: '16bit' }); * * @throws Error if width/height are out of bounds. */ constructor(origin: Vector2, zOrder: number, width: number, height: number, options?: LayerOptions | boolean); /** * Returns whether the layer is a macro layer. */ getIsMacroLayer(): boolean; /** * Returns the layer's charCode mode (immutable). * * @returns `'8bit'` or `'16bit'`. */ getCharCodeMode(): CharCodeMode; /** * Configures the layer's execution mode. * @internal */ setMode(mode: CoreMode): void; /** * Injects the SpriteRegistry into the layer. * @internal */ setSpriteRegistry(registry: SpriteRegistry): void; /** * Returns all orders currently stored in this layer. * * @returns The order list. */ getOrders(): AnyNetworkOrder[]; /** * Adds orders to the layer (incremental mode). * Re-rasterizes all orders to ensure consistent state. * * @param orders - Orders to append. * * @example * layer.addOrders([orderA, orderB]); */ addOrders(orders: AnyNetworkOrder[]): void; /** * Adds temporary orders that are rasterized but not stored. * Used for macro-generated content (particles/UI) regenerated each frame. * * @param orders - Orders to rasterize temporarily. */ addTemporaryOrders(orders: AnyNetworkOrder[]): void; /** * Replaces all orders in the layer (reset mode). * Automatically rasterizes all orders. * * @param orders - New orders to set. */ setOrders(orders: AnyNetworkOrder[]): void; /** * Clears all orders and resets the buffer. */ clearOrders(): void; /** * Returns the layer origin in world space. * * @returns The current origin. */ getOrigin(): Vector2; /** * Updates the layer's origin. * * @param origin - New layer origin. */ setOrigin(origin: Vector2): void; /** * Returns the layer's z-order. * * @returns Z-order value. */ getZOrder(): number; /** * Updates the layer's z-order. * * @param zOrder - New z-order. */ setZOrder(zOrder: number): void; /** * Returns the layer's unique ID. * * @returns Layer ID (0-65535). */ getId(): number; /** * Returns the layer's optional name (debug/metadata only). * * @returns Name or `undefined`. */ getName(): string | undefined; /** * Sets the layer's optional name (debug/metadata only). * * @param name - New name or `undefined` to clear. */ setName(name: string | undefined): void; /** * Returns a debug-friendly snapshot of this layer (metadata only). * * @returns Debug info snapshot. */ getDebugInfo(): { id: number; name?: string; z: number; mustBeReliable: boolean; charCodeMode: CharCodeMode; width: number; height: number; origin: { x: number; y: number; }; enabled: boolean; useSetMode: boolean; needsCommit: boolean; ordersCount: number; }; /** * Sets the layer's unique ID. * @internal * @param id - Unique layer ID (0-65535). */ private setIdInternal; /** * Returns the internal cell buffer. * @returns The `CellBuffer` instance. * @internal */ getData(): CellBuffer; /** * Returns the layer width in cells. * @returns Width in cells. */ getWidth(): number; /** * Returns the layer height in cells. * @returns Height in cells. */ getHeight(): number; /** * Returns the cell at the given coordinates. * * @param x - X coordinate (0..width-1). * @param y - Y coordinate (0..height-1). * @returns The cell or `null` if out of bounds. */ getCellAt(x: number, y: number): Cell | null; /** * Marks this layer as reliable or volatile for network transport. * * @param mustBeReliable - `true` for reliable channel, `false` for volatile. */ setMustBeReliable(mustBeReliable: boolean): void; /** * Returns whether this layer must be sent reliably. * * @returns `true` if reliable. */ getMustBeReliable(): boolean; /** * Enables or disables the layer. * A disabled layer will not be rendered by the display rasterizer. * * @param enabled - `true` to enable, `false` to disable. */ setEnabled(enabled: boolean): void; /** * Returns whether the layer is enabled. * * @returns `true` if enabled. */ isEnabled(): boolean; /** * Returns `true` if the origin changed since the last tick. * @internal */ hasOriginChanged(): boolean; /** * Returns `true` if the z-order changed since the last tick. * @internal */ hasZOrderChanged(): boolean; /** * Calculates UpdateFlags based on layer state and changes. * @internal * @returns Bitpacked flags (0x00 to 0x0F). * * Flag structure: * - Bit 0 (0x01): Layer Enabled (1=active, 0=disabled) * - Bit 1 (0x02): SET Mode (1=SET, 0=ADD) * - Bit 2 (0x04): Position Changed * - Bit 3 (0x08): Z-Order Changed */ calculateUpdateFlags(): number; /** * Resets change tracking after sending a tick. * @internal */ resetChangeTracking(): void; /** * Marks the layer as needing to be sent on the next tick. * * Used to optimize bandwidth by sending only modified layers. * * @example * layer.addOrders([...]); * layer.commit(); */ commit(): void; /** * Returns a breakdown of order types for debugging. * @internal */ private getOrdersBreakdown; /** * Returns `true` if the layer needs to be sent. * @internal */ getNeedsCommit(): boolean; /** * Resets the commit flag after sending. * @internal */ resetCommit(): void; } /** * DisplayOrigin defines the visible area of a display in world space */ interface DisplayOrigin { id: number; x: number; y: number; width: number; height: number; } /** * DisplayRasterizer * * Composes pre-rasterized layers (Layer.data[]) to produce the final grid * for a display, taking into account: * - Origin (which area of the world is visible) * - Z-order (layer stacking) * - Transparency (bgColor = 0 lets through, bgColor ≠ 0 blocks) * * Note: Layers have an IMMUTABLE size of 256×256 cells. * * Composition algorithm: * 1. Sort layers by DESCENDING Z-order (from HIGHEST to LOWEST) * 2. For each display cell: * a. Traverse layers from top to bottom * b. Take the first non-empty character encountered * c. Take the first non-transparent foreground color * d. If bgColor ≠ 0 → take and STOP (opaque, blocks layers below) * e. If bgColor = 0 → continue to next layers (transparent) * * Performance: * - Pure reading of Layer.data[] buffers (no order recalculation) * - Pre-calculated CSS cache to avoid repeated conversions (15-40x gain) * - Very fast even with many layers */ declare class DisplayRasterizer { private engine; /** * Pre-calculated RGB color cache (256 colors) * Avoids Map.get() and repeated accesses for each cell * Gain: ~15-40x faster on conversions */ private colorCache; /** * Pre-calculated character cache * Uses Map for lazy memoization to support full 16-bit charCodes (0-65535) * Pre-populated with first 256 chars for backward compatibility * Gain: ~20-40% faster on character assignment */ private static charCache; /** * Gets the cached character for a charCode, creating it lazily if needed * Supports full 16-bit range (0-65535) for atlas indices */ private static getChar; /** * Reusable buffers for cells and opacity mask * Avoids allocations on every rasterize() call * Gain: ~10-15% faster (no GC pressure) */ private cellsBuffer; private opacityBuffer; private visibleLayersBuffer; private visibleLayerIdsBuffer; private passCellsBuffers; private passOpacityBuffers; /** * Last computed visible layer IDs (sorted in actual composition order). * Updated on every rasterize() call. */ getLastVisibleLayerIds(): ReadonlyArray; constructor(engine: Core); /** * Rebuilds the RGB cache from the engine palette * Call after palette modification (setColor, resetPalette) */ rebuildColorCache(): void; /** * Direct access to RGB cache (O(1) instead of Map.get + calculations) */ private getColorRGB; /** * Gets the emission value of a color (0-255) */ private getColorEmission; /** * Checks if a layer is visible in the display viewport * Optimization: allows filtering off-screen layers before sorting */ private isLayerVisible; /** * Rasterizes a display by composing its visible layers * OPTIMIZED: Returns palette indices instead of RGB for optimal cache * * @param origin - Area of the world visible by the display * @param layers - All user layers * @returns Render state with palette indices + RGB palette */ rasterize(origin: DisplayOrigin, layers: Layer[], renderPasses?: RenderPassConfig[]): RenderState; private rasterizeSinglePass; private rasterizeMultiPass; private composePass; private preparePassBuffers; private ensurePassBuffers; private normalizePasses; /** * Variant that accepts an already sorted layer array (optimization) * Note: Still applies visibility filtering for optimization */ rasterizeWithSortedLayers(origin: DisplayOrigin, sortedLayers: Layer[]): RenderState; /** * Calculates the intersection between an origin and a layer's bounds * Useful for optimizing by only processing visible layers */ private calculateIntersection; } /** * OrderBuilder - Factory to create orders easily and type-safely * * Instead of manually writing complex structures, use these helpers: * * @example Before (verbose and error-prone) * ```typescript * const order = { * type: 0x0a, * shapeType: 0x01, * shapeData: { * posX: 10, * posY: 20, * width: 5, * height: 3, * filled: true, * charCode: 0x31, * bgColorCode: 1, * fgColorCode: 15 * } * }; * ``` * * @example After (simple and type-safe with string support!) * ```typescript * // Both syntaxes work: * const order1 = OrderBuilder.rectangle(10, 20, 5, 3, { * charCode: '1', // ✨ NEW: Use string directly! * bgColor: 1, * fgColor: 15, * filled: true * }); * * const order2 = OrderBuilder.char(10, 20, '#', 15, 0); // ✨ String support! * const order3 = OrderBuilder.char(10, 20, 0x23, 15, 0); // Number still works * ``` */ /** * Common options for graphical orders */ interface ColorOptions { charCode?: string | number; bgColor?: number; fgColor?: number; } /** * OrderBuilder - Static factory to create orders */ declare class OrderBuilder { /** * Converts a string or number to a char code. * * @param char - String (first character used) or numeric char code (0-65535). * @returns Char code (0-65535). * * @example * OrderBuilder.toCharCode('#') // → 35 * OrderBuilder.toCharCode(35) // → 35 * OrderBuilder.toCharCode('ABC') // → 65 (first char only) * OrderBuilder.toCharCode(256) // → 256 (preserved for 16-bit layers) */ static toCharCode(char: string | number): number; /** * Removes common leading whitespace from multiline strings (dedent). * Useful for template literals that are indented in source code. * * @param text - Multiline text to dedent. * @returns Dedented text. * * @example * const text = ` * Hello * World * `; * OrderBuilder.dedent(text); // → "Hello\nWorld" */ static dedent(text: string): string; /** * Encodes a string into CP437 character codes packed into a string. * This ensures that strings with Unicode characters (e.g. '░', 'é') are * converted to their CP437 byte values (e.g. 176, 130) before being stored. * * @param text - The input string (possibly containing Unicode) * @returns A string where each character is a CP437 byte value (0-255) */ static encodeString(text: string): string; /** * Creates a single character order at a position. * * @param x - X position (cell). * @param y - Y position (cell). * @param char - Character as string ('#') or numeric code (35). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Char order. * * @example * OrderBuilder.char(10, 20, '#', 15, 0); */ static char(x: number, y: number, char: string | number, fgColor?: number, bgColor?: number): CharOrder; /** * Creates a single-line text order. * * @param x - X position (cell). * @param y - Y position (cell). * @param text - Text to draw. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Text order. * * @example * OrderBuilder.text(2, 4, 'Hello', 15, 0); */ static text(x: number, y: number, text: string, fgColor?: number, bgColor?: number): TextOrder; /** * Creates a multi-line text order (\n for line breaks). * Automatically dedents template literals. * * @param x - X position (cell). * @param y - Y position (cell). * @param text - Text content (can include \n). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Multiline text order. * * @example With explicit \n * OrderBuilder.textMultiline(10, 5, 'Hello\nWorld\n!', 15, 0); * * @example With template literal (auto-dedented) * OrderBuilder.textMultiline(10, 5, ` * Score: ${score} * Lives: ${lives} * Level: ${level} * `, 15, 0); */ static textMultiline(x: number, y: number, text: string, fgColor?: number, bgColor?: number): TextMultilineOrder; /** * Creates a rectangular frame with uniform colors. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param frame - Array of characters as strings or numbers. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Sub-frame order. */ static subFrame(x: number, y: number, width: number, height: number, frame: (string | number)[], fgColor?: number, bgColor?: number): SubFrameOrder; /** * Creates a rectangular frame with per-cell colors. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param frame - Array of cells where `charCode` can be string or number. * @returns Sub-frame order (multi-color). */ static subFrameMultiColor(x: number, y: number, width: number, height: number, frame: Array<{ charCode: string | number; bgColorCode: number; fgColorCode: number; }>): SubFrameMultiColorOrder; /** * Creates a full-frame fill for the entire layer (256×256). * * @param frame - Array of characters as strings or numbers. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Full-frame order. */ static fullFrame(frame: (string | number)[], fgColor?: number, bgColor?: number): FullFrameOrder; /** * Creates a full-frame fill with per-cell colors (256×256). * * @param frame - Array of cells where `charCode` can be string or number. * @returns Full-frame order (multi-color). */ static fullFrameMultiColor(frame: Array<{ charCode: string | number; bgColorCode: number; fgColorCode: number; }>): FullFrameMultiColorOrder; /** * Creates a single-color sprite order at a position. * * @param x - X position (cell). * @param y - Y position (cell). * @param spriteIndex - Sprite index in the registry. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Sprite order. */ static sprite(x: number, y: number, spriteIndex: number, fgColor?: number, bgColor?: number): SpriteOrder; /** * Creates a multi-color sprite order at a position. * * @param x - X position (cell). * @param y - Y position (cell). * @param spriteIndex - Sprite index in the registry. * @returns Multi-color sprite order. */ static spriteMultiColor(x: number, y: number, spriteIndex: number): SpriteMultiColorOrder; /** * Applies colors without changing characters. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param colorData - Array of fg/bg colors per cell. * @returns Color map order. */ static colorMap(x: number, y: number, width: number, height: number, colorData: Array<{ fgColorCode: number; bgColorCode: number; }>): ColorMapOrder; /** * Creates a rectangle shape order. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param options - Shape options. * @param options.charCode - Character as string ('█') or numeric code (9608). * @param options.bgColor - Background color (0-255). * @param options.fgColor - Foreground color (0-255). * @param options.filled - Fill shape (default: true). * @returns Shape order. */ static rectangle(x: number, y: number, width: number, height: number, options?: { charCode?: string | number; bgColor?: number; fgColor?: number; filled?: boolean; }): ShapeOrder; /** * Creates a circle shape order. * * @param centerX - Center X position (cell). * @param centerY - Center Y position (cell). * @param radius - Radius in cells. * @param options - Shape options. * @param options.charCode - Character as string ('█') or numeric code (9608). * @param options.bgColor - Background color (0-255). * @param options.fgColor - Foreground color (0-255). * @param options.filled - Fill shape (default: true). * @returns Shape order. */ static circle(centerX: number, centerY: number, radius: number, options?: { charCode?: string | number; bgColor?: number; fgColor?: number; filled?: boolean; }): ShapeOrder; /** * Creates a line shape order. * * @param x1 - Start X position (cell). * @param y1 - Start Y position (cell). * @param x2 - End X position (cell). * @param y2 - End Y position (cell). * @param options - Shape options. * @param options.charCode - Character as string ('█') or numeric code (9608). * @param options.bgColor - Background color (0-255). * @param options.fgColor - Foreground color (0-255). * @returns Shape order. */ static line(x1: number, y1: number, x2: number, y2: number, options?: { charCode?: string | number; bgColor?: number; fgColor?: number; }): ShapeOrder; /** * Creates an ellipse shape order. * * @param centerX - Center X position (cell). * @param centerY - Center Y position (cell). * @param radiusX - Radius on X axis (cells). * @param radiusY - Radius on Y axis (cells). * @param options - Shape options. * @param options.charCode - Character as string ('█') or numeric code (9608). * @param options.bgColor - Background color (0-255). * @param options.fgColor - Foreground color (0-255). * @param options.filled - Fill shape (default: true). * @returns Shape order. */ static ellipse(centerX: number, centerY: number, radiusX: number, radiusY: number, options?: { charCode?: string | number; bgColor?: number; fgColor?: number; filled?: boolean; }): ShapeOrder; /** * Creates a triangle shape order. * * @param x1 - First point X (cell). * @param y1 - First point Y (cell). * @param x2 - Second point X (cell). * @param y2 - Second point Y (cell). * @param x3 - Third point X (cell). * @param y3 - Third point Y (cell). * @param options - Shape options. * @param options.charCode - Character as string ('█') or numeric code (9608). * @param options.bgColor - Background color (0-255). * @param options.fgColor - Foreground color (0-255). * @param options.filled - Fill shape (default: true). * @returns Shape order. */ static triangle(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, options?: { charCode?: string | number; bgColor?: number; fgColor?: number; filled?: boolean; }): ShapeOrder; /** * Creates a dot cloud with the same character at multiple positions. * * @param positions - Array of positions. * @param char - Character as string ('#') or numeric code (35). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Dot cloud order. */ static dotCloud(positions: Array<{ posX: number; posY: number; }>, char: string | number, fgColor?: number, bgColor?: number): DotCloudOrder; /** * Creates a multi-color dot cloud (per-dot colors and characters). * * @param dots - Array where `charCode` can be string or number. * @returns Dot cloud order (multi-color). */ static dotCloudMultiColor(dots: Array<{ posX: number; posY: number; charCode: string | number; bgColorCode: number; fgColorCode: number; }>): DotCloudMultiColorOrder; /** * Creates a bitmask order with a uniform character. * Useful for ore veins, destructible terrain, collision maps, or fog of war. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param mask - Flat array of booleans (row-major order: sizeX × sizeY). * @param char - Character as string ('#') or numeric code (35). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @param override - true: clear absences (transparent), false: preserve existing cells. * @returns Bitmask order. * * @example Create a 3×3 cross pattern * OrderBuilder.bitmask(10, 10, 3, 3, [ * false, true, false, * true, true, true, * false, true, false * ], '#', 15, 0, false); */ static bitmask(x: number, y: number, width: number, height: number, mask: boolean[], char: string | number, fgColor?: number, bgColor?: number, override?: boolean): BitmaskOrder; /** * Creates a bitmask order with 3 visual variants (values 1..3). * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param mask - Flat array of values 0-3 (row-major order: sizeX × sizeY). * 0 = absence, 1-3 = variant index. * @param variants - Array of up to 3 variants { char, fgColor, bgColor } for values 1..3. * @param override - true: clear absences (transparent), false: preserve existing cells. * @returns Bitmask4 order. * * @example Create a 3×3 pattern with 3 variants * OrderBuilder.bitmask4(10, 10, 3, 3, [ * 0, 1, 0, * 2, 3, 2, * 0, 1, 0 * ], [ * { char: 'a', fgColor: 15, bgColor: 0 }, * { char: 'b', fgColor: 14, bgColor: 0 }, * { char: 'c', fgColor: 13, bgColor: 0 } * ], false); */ static bitmask4(x: number, y: number, width: number, height: number, mask: number[], variants: Array<{ char: string | number; fgColor: number; bgColor: number; }>, override?: boolean): Bitmask4Order; /** * Creates a bitmask order with up to 15 visual variants (values 1..15). * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param mask - Flat array of values 0-15 (row-major order: sizeX × sizeY). * 0 = absence, 1-15 = variant index. * @param variants - Array of up to 15 variants { char, fgColor, bgColor } for values 1..15. * @param override - true: clear absences (transparent), false: preserve existing cells. * @returns Bitmask16 order. * * @example Create a 3×3 pattern with multiple variants * OrderBuilder.bitmask16(10, 10, 3, 3, [ * 0, 1, 0, * 2, 15, 2, * 0, 1, 0 * ], [ * { char: 'a', fgColor: 15, bgColor: 0 }, * { char: 'b', fgColor: 14, bgColor: 0 }, * // ... up to 15 variants * ], false); */ static bitmask16(x: number, y: number, width: number, height: number, mask: number[], variants: Array<{ char: string | number; fgColor: number; bgColor: number; }>, override?: boolean): Bitmask16Order; /** * Creates a sprite cloud with a single-color sprite at multiple positions. * * @param spriteIndex - Sprite index in the registry. * @param positions - Array of positions. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Sprite cloud order. */ static spriteCloud(spriteIndex: number, positions: Array<{ posX: number; posY: number; }>, fgColor?: number, bgColor?: number): SpriteCloudOrder; /** * Creates a multi-color sprite cloud at multiple positions. * * @param spriteIndex - Sprite index in the registry. * @param positions - Array of positions. * @returns Multi-color sprite cloud order. */ static spriteCloudMultiColor(spriteIndex: number, positions: Array<{ posX: number; posY: number; }>): SpriteCloudMultiColorOrder; /** * Creates a varied sprite cloud (single-color sprites per position). * * @param sprites - Array of sprites with position and colors. * @returns Varied sprite cloud order. */ static spriteCloudVaried(sprites: Array<{ posX: number; posY: number; spriteIndex: number; bgColorCode: number; fgColorCode: number; }>): SpriteCloudVariedOrder; /** * Creates a varied sprite cloud with multi-color sprites. * * @param sprites - Array of sprites with position. * @returns Varied multi-color sprite cloud order. */ static spriteCloudVariedMultiColor(sprites: Array<{ posX: number; posY: number; spriteIndex: number; }>): SpriteCloudVariedMultiColorOrder; /** * Fills the entire layer with a character. * * @param char - Character as string (' ') or numeric code (32). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Fill order. */ static fill(char?: string | number, fgColor?: number, bgColor?: number): FillOrder; /** * Fills the layer with a repeating character pattern. * * @param patternWidth - Pattern width in cells. * @param patternHeight - Pattern height in cells. * @param pattern - Array of characters as strings or numbers. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Fill character order. */ static fillChar(patternWidth: number, patternHeight: number, pattern: (string | number)[], fgColor?: number, bgColor?: number): FillCharOrder; /** * Fills the layer with a repeating single-color sprite. * * @param spriteIndex - Sprite index in the registry. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Fill sprite order. */ static fillSprite(spriteIndex: number, fgColor?: number, bgColor?: number): FillSpriteOrder; /** * Fills the layer with a repeating multi-color sprite. * * @param spriteIndex - Sprite index in the registry. * @returns Fill multi-color sprite order. */ static fillSpriteMultiColor(spriteIndex: number): FillSpriteMultiColorOrder; /** * Creates a rectangle with a colored border and a different interior. * * @param x - X position (cell). * @param y - Y position (cell). * @param width - Width in cells. * @param height - Height in cells. * @param borderOptions - Border colors/char. * @param borderOptions.charCode - Character as string ('█') or numeric code (9608). * @param fillOptions - Fill colors/char. * @param fillOptions.charCode - Character as string ('█') or numeric code (9608). * @returns Array of shape orders (fill + border). */ static boxWithBorder(x: number, y: number, width: number, height: number, borderOptions: ColorOptions & { charCode?: string | number; }, fillOptions: ColorOptions & { charCode?: string | number; }): ShapeOrder[]; /** * Creates a grid of points. * * @param startX - Grid start X (cell). * @param startY - Grid start Y (cell). * @param cellWidth - Cell width in cells. * @param cellHeight - Cell height in cells. * @param rows - Number of rows. * @param cols - Number of columns. * @param options - Color options. * @param options.charCode - Character as string ('+') or numeric code (43). * @returns Dot cloud order. */ static grid(startX: number, startY: number, cellWidth: number, cellHeight: number, rows: number, cols: number, options?: ColorOptions): DotCloudOrder; /** * Creates a polyline (connected line segments through multiple points). * Uses Bresenham algorithm between consecutive points. * * @param points - Array of points to connect (minimum 2 for a line). * @param char - Character as string ('*') or numeric code (42). * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Polyline order. * * @example Simple line * ```typescript * OrderBuilder.polyline( * [{ x: 0, y: 0 }, { x: 10, y: 5 }], * '*', 14, 0 * ); * ``` * * @example Path with multiple points * ```typescript * OrderBuilder.polyline( * [ * { x: 10, y: 10 }, * { x: 20, y: 15 }, * { x: 30, y: 10 }, * { x: 40, y: 20 } * ], * '#', 11, 0 * ); * ``` */ static polyline(points: Array<{ x: number; y: number; }>, char?: string | number, fgColor?: number, bgColor?: number): PolylineOrder; /** * Creates a polyline from a flat coordinate array. * * @param coords - Flat array of coordinates [x1, y1, x2, y2, ...]. * @param char - Character as string ('*') or numeric code. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Polyline order. * * @example * ```typescript * OrderBuilder.polylineFromCoords( * [0, 0, 10, 5, 20, 0], // 3 points * '-', 14 * ); * ``` */ static polylineFromCoords(coords: number[], char?: string | number, fgColor?: number, bgColor?: number): PolylineOrder; /** * Creates a closed polygon (polyline that returns to start). * * @param points - Array of points forming the polygon. * @param char - Character as string ('*') or numeric code. * @param fgColor - Foreground color (0-255). * @param bgColor - Background color (0-255). * @returns Polyline order. * * @example Triangle * ```typescript * OrderBuilder.polygon( * [ * { x: 20, y: 5 }, * { x: 10, y: 15 }, * { x: 30, y: 15 } * ], * '#', 12 * ); * ``` */ static polygon(points: Array<{ x: number; y: number; }>, char?: string | number, fgColor?: number, bgColor?: number): PolylineOrder; } export { BufferCompat, CellBuffer, Core, DisplayDecoder, DisplayEncoder, DisplayRasterizer, Layer, LayerDecoder, LayerEncoder, LayerRasterizer, LoadDecoder, LoadEncoder, MacroEventDecoder, MacroEventEncoder, MacroLoadDecoder, MacroLoadEncoder, MacroOrderDecoder, MacroOrderEncoder, OrderBuilder, OrderDecoder, OrderEncoder, ShapeType, SpriteRegistry, UpdatePacketDecoder, UpdatePacketEncoder }; export type { Bitmask16Order, Bitmask4Order, BitmaskOrder, CharOrder, ColorMapOrder, DotCloudMultiColorOrder, DotCloudOrder, FillCharOrder, FillOrder, FillSpriteMultiColorOrder, FillSpriteOrder, FullFrameMultiColorOrder, FullFrameOrder, PolylineOrder, ShapeOrder, SpriteCloudMultiColorOrder, SpriteCloudOrder, SpriteCloudVariedMultiColorOrder, SpriteCloudVariedOrder, SpriteMultiColorOrder, SpriteOrder, SubFrameMultiColorOrder, SubFrameOrder, TextMultilineOrder, TextOrder };