@utsp/core
Version:
UTSP core engine - Universal Text Stream Protocol data management
1,790 lines (1,775 loc) • 167 kB
TypeScript
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[])