UNPKG

@utsp/core

Version:

UTSP core engine - Universal Text Stream Protocol data management

1,790 lines (1,775 loc) 167 kB
import * as _utsp_types from '@utsp/types'; import { Vector2, AxisSource, ButtonSource, InputBindingLoadPacket, AxisBinding, ButtonBinding, SoundInstanceId, AudioConfigCommand, PlaySoundCommand, StopSoundCommand, FadeOutSoundCommand, PauseSoundCommand, ResumeSoundCommand, IAudioProcessor, SoundFormat, SoundLoadType, SoundLoadPacket, SoundExternalLoadPacket, UserRenderState } from '@utsp/types'; export { AxisBinding, AxisSource, ButtonBinding, ButtonSource, InputBindingLoadPacket, RenderState, RenderedCell, UserRenderState, Vector2 } from '@utsp/types'; /** * Font system for UTSP Core * Supports WebFont (CSS-based) and BitmapFont (pixel-based) */ /** * Font type enumeration */ declare enum FontType { Web = "web", Bitmap = "bitmap" } /** * WebFont configuration * For CSS-based fonts rendered by the browser */ interface WebFontConfig { fontFamily: string; fontSize: number; offsetX?: number; offsetY?: number; charSpacing?: number; lineHeight?: number; fontWeight?: string; fontStyle?: string; } /** * WebFont class * Represents a CSS-based font for browser rendering */ declare class WebFont { private fontId; private config; constructor(fontId: number, config: WebFontConfig); /** * Get the unique font ID */ getFontId(): number; /** * Get the full configuration (copy) */ getConfig(): WebFontConfig; /** * Get the font family */ getFontFamily(): string; /** * Get the font size in pixels */ getFontSize(): number; /** * Get the horizontal offset */ getOffsetX(): number; /** * Get the vertical offset */ getOffsetY(): number; /** * Get the character spacing */ getCharSpacing(): number; /** * Get the line height multiplier */ getLineHeight(): number; /** * Get the font weight */ getFontWeight(): string; /** * Get the font style */ getFontStyle(): string; /** * Generate CSS declaration for this font * @returns CSS string (e.g., "font-family: Consolas; font-size: 16px;") */ toCSS(): string; } /** * BitmapFont configuration * For pixel-based fonts with custom glyph data */ interface BitmapFontConfig { charWidth: number; charHeight: number; cellWidth?: number; cellHeight?: number; glyphs: Map<number, Uint8Array>; } /** * BitmapFont class * Represents a pixel-based font with custom glyph bitmaps * Corresponds to LoadType 0x04 (Charset) in UTSP protocol */ declare class BitmapFont { private fontId; private config; constructor(fontId: number, config: BitmapFontConfig); /** * Get the unique font ID */ getFontId(): number; /** * Get the full configuration (deep copy) */ getConfig(): BitmapFontConfig; /** * Get the glyph width in pixels (actual bitmap size) */ getCharWidth(): number; /** * Get the glyph height in pixels (actual bitmap size) */ getCharHeight(): number; /** * Get the target cell width in pixels (rendering size) * Defaults to glyph width if not specified */ getCellWidth(): number; /** * Get the target cell height in pixels (rendering size) * Defaults to glyph height if not specified */ getCellHeight(): number; /** * Get the bitmap data for a specific character * @param charCode Character code (0-255) * @returns Bitmap data or undefined if not found */ getGlyph(charCode: number): Uint8Array | undefined; /** * Check if a glyph exists for a character * @param charCode Character code (0-255) */ hasGlyph(charCode: number): boolean; /** * Get the number of glyphs in this font */ getGlyphCount(): number; /** * Get all character codes that have glyphs */ getCharCodes(): number[]; } /** * 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; } /** * 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; } /** * 0x13 - Clear: Fills entire layer with single character and colors */ interface ClearOrder 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; } /** * 0x20 - TriggerSound: Triggers positional sound effect */ interface TriggerSoundOrder extends NetworkOrder { type: 0x20; posX: number; posY: number; soundId: number; loop: boolean; } /** * 0x21 - TriggerGlobalSound: Triggers global (non-positional) sound */ interface TriggerGlobalSoundOrder extends NetworkOrder { type: 0x21; soundId: number; loop: boolean; } type AnyNetworkOrder = CharOrder | TextOrder | TextMultilineOrder | SubFrameOrder | SubFrameMultiColorOrder | FullFrameOrder | FullFrameMultiColorOrder | SpriteOrder | SpriteMultiColorOrder | ColorMapOrder | ShapeOrder | DotCloudOrder | DotCloudMultiColorOrder | BitmaskOrder | Bitmask4Order | Bitmask16Order | SpriteCloudOrder | SpriteCloudMultiColorOrder | SpriteCloudVariedOrder | SpriteCloudVariedMultiColorOrder | ClearOrder | FillCharOrder | FillSpriteOrder | FillSpriteMultiColorOrder | TriggerSoundOrder | TriggerGlobalSoundOrder; interface NetworkLayer { id: number; updateFlags: number; zIndex: number; originX: number; originY: number; width: number; height: number; orderCount: number; orders: AnyNetworkOrder[]; } /** * Update packet according to the new UTSP protocol * Layers are at the User level, not under displays */ interface UpdatePacket { /** Tick counter (8 bytes) */ tick: number; /** Number of displays (2 bytes) */ 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[]; } /** * Represents a display (camera) in the virtual world * * ARCHITECTURE (new protocol): * - Display = camera looking into the virtual world * - LAYERS are NO LONGER in Display, they are at User level * - Display only defines WHICH PART of the world we see (origin) */ declare class Display { private id; private origin; private size; private previousOrigin; private previousSize; private toDraw; constructor(id?: number, sizeX?: number, sizeY?: number); /** * Gets the display ID (0-255) */ getId(): number; /** * Gets the origin position in the world */ getOrigin(): Vector2; /** * Sets the origin position in the world */ setOrigin(origin: Vector2): void; /** * Moves the origin in the world */ moveOrigin(deltaX: number, deltaY: number): void; /** * Checks if display origin has changed since last tick * @internal - Used to calculate if update should be sent */ hasOriginChanged(): boolean; /** * Checks if display size has changed since last tick * @internal - Used to calculate if update should be sent */ hasSizeChanged(): boolean; /** * Checks if display has changed (origin OR size) * @internal */ hasChanged(): boolean; /** * Resets change tracking * @internal - Called by Core.endTick() after sending updates */ resetChangeTracking(): void; /** * Gets the display size */ getSize(): Vector2; /** * Sets the display size */ setSize(size: Vector2): void; } interface Cell { charCode: number; fgColorCode: number; bgColorCode: number; } /** * Optimized buffer for 65,536 cells with TypedArray * Interleaved structure: [char0, fg0, bg0, char1, fg1, bg1, ...] * * Performance: * - clear(): ~5-10μs (vs 826μs with object array) * - Better cache locality * - Less GC pressure */ declare class CellBuffer { private data; private size; constructor(size?: number); /** * 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 * Reduces memory writes by 66% (writes chars only, then batch-fills 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 batch operations */ getRawData(): Uint8Array; /** * Buffer size (number of cells) */ getSize(): number; } /** * Optimized buffer to store only charcodes (unicolor sprites) * Simple structure: [char0, char1, char2, ...] * * Performance: * - 3x less memory than CellBuffer (1 byte vs 3 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(): Uint8Array; /** * 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; } /** * 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, BitmapFont = 4,// Formerly "Charset" - pixel-based fonts Sound = 5, WebFont = 6 } /** * 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 */ interface ColorPaletteLoad { loadType: LoadType.ColorPalette; 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[]; }>; } /** * BitmapFont Load (LoadType 0x04) * Custom character definitions (monochrome bitmaps) * Formerly known as "Charset" - renamed to BitmapFont for clarity */ interface BitmapFontLoad { loadType: LoadType.BitmapFont; fontId: number; width: number; height: number; cellWidth: number; cellHeight: number; characters: Array<{ charCode: number; bitmap: Uint8Array; }>; } /** * WebFont Load (LoadType 0x06) * CSS-based font definitions for browser rendering * Note: The default font is "Courier New" (monospace), but users can specify non-monospace fonts */ interface WebFontLoad { loadType: LoadType.WebFont; fontId: number; fontFamily: string; fontSize: number; offsetX?: number; offsetY?: number; charSpacing?: number; lineHeight?: number; fontWeight?: string; fontStyle?: string; } /** * Sound Load (LoadType 0x05) * MIDI sound data for audio playback */ interface SoundLoad { loadType: LoadType.Sound; sounds: Array<{ soundId: number; midiData: Uint8Array; }>; } /** * Union type for all load types */ type AnyLoad = ColorPaletteLoad | SpriteLoad | MulticolorSpriteLoad | BitmapFontLoad | SoundLoad | WebFontLoad; /** * 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 a LoadPacket * Converts network format to optimized internal format */ loadUnicolorSprites(loadData: SpriteLoad): void; /** * Loads multicolor sprites from a LoadPacket * Converts network format to optimized internal format */ loadMulticolorSprites(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; } declare class Layer { private id; private origin; private orders; private zOrder; private data; private width; private height; private isStatic; private spriteRegistry?; private mode; private previousOrigin; private previousZOrder; private enabled; private useSetMode; private needsCommit; private static rasterizer; constructor(origin: Vector2, zOrder: number, width: number, height: number, isStatic?: boolean); /** * Configures the layer's execution mode (called by User) * @internal */ setMode(mode: CoreMode): void; /** * Injects the SpriteRegistry into the layer (called by Core) * @internal */ setSpriteRegistry(registry: SpriteRegistry): void; getOrders(): AnyNetworkOrder[]; /** * Adds orders to the layer (incremental mode) * Automatically rasterizes new orders in ADD mode */ addOrders(orders: AnyNetworkOrder[]): void; /** * Replaces all orders in the layer (reset mode) * Automatically rasterizes all orders in SET mode */ setOrders(orders: AnyNetworkOrder[]): void; /** * Clears all orders and cleans the buffer */ clearOrders(): void; getOrigin(): Vector2; /** * Updates the layer's origin and rasterizes if necessary * * In client mode, this method automatically rasterizes the layer * to reflect the new position. * * @param origin - New layer origin */ setOrigin(origin: Vector2): void; getZOrder(): number; /** * Updates the layer's z-order * * @param zOrder - New z-order */ setZOrder(zOrder: number): void; /** * Gets the layer's unique ID * * Used for client-side reconstruction when applying UpdatePackets. * The ID allows associating a NetworkLayer with its corresponding Layer. * * @returns Layer ID (0-65535) */ getId(): number; /** * Sets the layer's unique ID * * @internal - Used by User.applyUpdate() during reconstruction * @param id - Unique layer ID (0-65535) */ setId(id: number): void; getData(): CellBuffer; getWidth(): number; getHeight(): number; getCellAt(x: number, y: number): Cell | null; /** * Marks this layer as static or dynamic * * Static layer: Sent only once to client via reliable channel, * but continues to be rasterized client-side every frame. * Used for UI elements, backgrounds, elements that never change. * * Dynamic layer: Sent every tick via volatile channel. * Used for animated elements, players, projectiles, etc. * * @param isStatic - true for static, false for dynamic */ setStatic(isStatic: boolean): void; /** * Checks if this layer is static */ getStatic(): boolean; /** * Enables or disables the layer * A disabled layer will not be rendered by DisplayRasterizer * * @param enabled - true to enable, false to disable */ setEnabled(enabled: boolean): void; /** * Checks if layer is enabled */ isEnabled(): boolean; /** * Checks if layer origin changed since last tick * @internal - Used to calculate UpdateFlags */ hasOriginChanged(): boolean; /** * Checks if layer z-order changed since last tick * @internal - Used to calculate UpdateFlags */ hasZOrderChanged(): boolean; /** * Calculates UpdateFlags based on layer state and changes * @internal - Used by Core.endTick() * @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 - Used by Core.endTick() */ resetChangeTracking(): void; /** * Marks the layer as needing to be sent on next tick * * Used to optimize bandwidth by sending only modified layers. * * @example * ```typescript * layer.addOrders([...]); * layer.commit(); // Layer will be sent on next tick * ``` */ commit(): void; /** * Checks if layer needs to be sent * @internal - Used by Core.endTickSplit() */ getNeedsCommit(): boolean; /** * Resets commit flag after sending * @internal - Used by Core.endTickSplit() */ resetCommit(): 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) * - Generate a JSON LoadPacket to send to client * - Decode compressed inputs received from client (future) */ declare class InputBindingRegistry { private axes; private buttons; private axisNameToId; private buttonNameToId; 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; /** * 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<sourceId, value>) * * @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, number>): 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<number, boolean>): 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; /** * Counts the number of defined axes * * @returns number of axes */ getAxisCount(): number; /** * Counts the number of defined buttons * * @returns number of buttons */ getButtonCount(): number; /** * Retrieves all axes * * @returns array of all axis bindings */ getAllAxes(): AxisBinding[]; /** * Retrieves all buttons * * @returns array of all button bindings */ getAllButtons(): ButtonBinding[]; /** * 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 all axes */ clearAxes(): void; /** * Removes all buttons */ clearButtons(): void; /** * Completely resets the registry */ clear(): void; /** * Displays a summary of bindings (for debug) * * @returns formatted string */ toString(): string; } /** * Per-user performance statistics * Collects user-specific metrics */ interface UserTickStats { userId: string; userName: string; tick: number; timestamp: number; displayCount: number; totalDisplayArea: number; totalLayers: number; visibleLayers: number; staticLayers: number; dynamicLayers: number; totalOrders: number; ordersByLayer: Map<number, number>; staticPacketSize: number; dynamicPacketSize: number; totalPacketSize: number; hasInput: boolean; axisCount: number; buttonCount: number; } declare class UserStats { private enabled; private currentStats; private _userId; private _userName; constructor(userId: string, userName: string); /** * Enables or disables statistics collection */ setEnabled(enabled: boolean): void; isEnabled(): boolean; /** User ID */ get userId(): string; /** User name */ get userName(): string; /** Current tick number */ get tick(): number; /** Current tick timestamp */ get timestamp(): number; /** Number of displays */ get displayCount(): number; /** Total display surface area (width × height) */ get totalDisplayArea(): number; /** Total number of layers */ get totalLayers(): number; /** Visible layers in displays */ get visibleLayers(): number; /** Static layers */ get staticLayers(): number; /** Dynamic layers */ get dynamicLayers(): number; /** Total orders for this user */ get totalOrders(): number; /** Orders par layer ID */ get ordersByLayer(): Map<number, number>; /** Taille du packet statique (octets) */ get staticPacketSize(): number; /** Taille du packet dynamique (octets) */ get dynamicPacketSize(): number; /** Taille totale des packets (static + dynamic) */ get totalPacketSize(): number; /** Estimated size after gzip compression (25% of original size) */ get compressedPacketSize(): number; /** If user sent input this tick */ get hasInput(): boolean; /** Number of bound axes */ get axisCount(): number; /** Number of bound buttons */ get buttonCount(): number; /** * @internal * Starts collection for a new tick */ startTick(tickNumber: number): void; /** * @internal * Records display information */ recordDisplays(displayCount: number, totalArea: number): void; /** * @internal * Enregistre les informations des layers */ recordLayers(total: number, visible: number, staticCount: number, dynamicCount: number): void; /** * @internal * Enregistre les orders d'un layer */ recordLayerOrders(layerId: number, orderCount: number): void; /** * @internal * Records network packet sizes */ recordPacketSizes(staticSize: number, dynamicSize: number): void; /** * @internal * Records input information */ recordInput(hasInput: boolean, axisCount: number, buttonCount: number): void; /** * @internal * Finalizes current tick stats */ endTick(): void; /** * Resets statistics */ 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<string, any>) */ declare class User<TData = Record<string, any>> { 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; private touchPositions; private activeTouchId; private inputBindings; private stats; private soundCommands; private nextSoundInstanceId; private audioConfigCommands; private lastListenerX; private lastListenerY; private pendingSendSounds; private audioProcessor?; /** * 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; getDisplays(): Display[]; /** * Adds a display to the user * * @param display - The display to add */ addDisplay(display: Display): void; /** * Removes a display from the user * * @param display - The display to remove * @returns true if display was removed, false otherwise */ removeDisplay(display: Display): boolean; /** * Removes all displays from the user */ clearDisplays(): void; getLayers(): Layer[]; /** * Adds a layer to the user * Layers are shared across all displays * * @param layer - The layer to add */ addLayer(layer: Layer): void; /** * Removes a layer from the user * * @param layer - The layer to remove * @returns true if layer was removed, false otherwise */ removeLayer(layer: Layer): boolean; /** * Removes all layers from the user */ 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[])