Fixed-Point Numbers
@esengine/ecs-framework-math provides deterministic fixed-point calculations designed for Lockstep architecture. Fixed-point numbers guarantee identical results across all platforms.
Why Fixed-Point?
Section titled “Why Fixed-Point?”Floating-point numbers may produce different rounding results on different platforms:
// Floating-point: may differ across platformsconst a = 0.1 + 0.2; // 0.30000000000000004 (some platforms) // 0.3 (other platforms)
// Fixed-point: consistent everywhereconst x = Fixed32.from(0.1);const y = Fixed32.from(0.2);const z = x.add(y); // raw = 19661 (all platforms)| Feature | Floating-Point | Fixed-Point |
|---|---|---|
| Cross-platform consistency | ❌ May differ | ✅ Identical |
| Network sync mode | State sync | Lockstep |
| Game types | FPS, RPG | RTS, MOBA, Fighting |
Installation
Section titled “Installation”npm install @esengine/ecs-framework-mathFixed32 Fixed-Point Number
Section titled “Fixed32 Fixed-Point Number”Q16.16 format: 16-bit integer + 16-bit fraction, range ±32767.99998.
Creating Fixed-Point Numbers
Section titled “Creating Fixed-Point Numbers”import { Fixed32 } from '@esengine/ecs-framework-math';
// From floating-pointconst speed = Fixed32.from(5.5);
// From integer (no precision loss)const count = Fixed32.fromInt(10);
// From raw value (after network receive)const received = Fixed32.fromRaw(360448); // equals 5.5
// Predefined constantsFixed32.ZERO // 0Fixed32.ONE // 1Fixed32.HALF // 0.5Fixed32.PI // πFixed32.TWO_PI // 2πFixed32.HALF_PI // π/2Basic Operations
Section titled “Basic Operations”const a = Fixed32.from(10);const b = Fixed32.from(3);
const sum = a.add(b); // 13const diff = a.sub(b); // 7const prod = a.mul(b); // 30const quot = a.div(b); // 3.333...const mod = a.mod(b); // 1const neg = a.neg(); // -10const abs = neg.abs(); // 10Comparison Operations
Section titled “Comparison Operations”const x = Fixed32.from(5);const y = Fixed32.from(3);
x.eq(y) // false - equalx.ne(y) // true - not equalx.lt(y) // false - less thanx.le(y) // false - less or equalx.gt(y) // true - greater thanx.ge(y) // true - greater or equal
x.isZero() // falsex.isPositive() // truex.isNegative() // falseMath Functions
Section titled “Math Functions”// Square root (Newton's method, deterministic)const sqrt = Fixed32.sqrt(Fixed32.from(16)); // 4
// RoundingFixed32.floor(Fixed32.from(3.7)) // 3Fixed32.ceil(Fixed32.from(3.2)) // 4Fixed32.round(Fixed32.from(3.5)) // 4
// ClampingFixed32.clamp(value, min, max)
// Linear interpolationFixed32.lerp(from, to, t)
// Min/MaxFixed32.min(a, b)Fixed32.max(a, b)Type Conversion
Section titled “Type Conversion”const value = Fixed32.from(3.14159);
// To float (for rendering)const float = value.toNumber(); // 3.14159
// Get raw value (for network)const raw = value.toRaw(); // 205887
// To integer (floor)const int = value.toInt(); // 3FixedVector2 Fixed-Point Vector
Section titled “FixedVector2 Fixed-Point Vector”Immutable 2D vector, all operations return new instances.
Creating Vectors
Section titled “Creating Vectors”import { FixedVector2, Fixed32 } from '@esengine/ecs-framework-math';
// From floating-pointconst pos = FixedVector2.from(100, 200);
// From raw values (after network receive)const received = FixedVector2.fromRaw(6553600, 13107200);
// From Fixed32const vec = new FixedVector2(Fixed32.from(10), Fixed32.from(20));
// Predefined constantsFixedVector2.ZERO // (0, 0)FixedVector2.ONE // (1, 1)FixedVector2.RIGHT // (1, 0)FixedVector2.LEFT // (-1, 0)FixedVector2.UP // (0, 1)FixedVector2.DOWN // (0, -1)Vector Operations
Section titled “Vector Operations”const a = FixedVector2.from(3, 4);const b = FixedVector2.from(1, 2);
// Basic operationsconst sum = a.add(b); // (4, 6)const diff = a.sub(b); // (2, 2)const scaled = a.mul(Fixed32.from(2)); // (6, 8)const divided = a.div(Fixed32.from(2)); // (1.5, 2)
// Vector productsconst dot = a.dot(b); // 3*1 + 4*2 = 11const cross = a.cross(b); // 3*2 - 4*1 = 2
// Lengthconst lenSq = a.lengthSquared(); // 25const len = a.length(); // 5
// Normalizeconst norm = a.normalize(); // (0.6, 0.8)
// Distanceconst dist = a.distanceTo(b); // sqrt((3-1)² + (4-2)²)Rotation and Angles
Section titled “Rotation and Angles”import { FixedMath } from '@esengine/ecs-framework-math';
const vec = FixedVector2.from(1, 0);const angle = Fixed32.from(Math.PI / 2); // 90 degrees
// Rotate vectorconst rotated = vec.rotate(angle); // (0, 1)
// Rotate around pointconst center = FixedVector2.from(5, 5);const around = vec.rotateAround(center, angle);
// Get vector angleconst vecAngle = vec.angle();
// Angle between vectorsconst between = vec.angleTo(other);
// Create unit vector from angleconst dir = FixedVector2.fromAngle(angle);
// From polar coordinatesconst polar = FixedVector2.fromPolar(length, angle);Type Conversion
Section titled “Type Conversion”const pos = FixedVector2.from(100.5, 200.5);
// To float object (for rendering)const obj = pos.toObject(); // { x: 100.5, y: 200.5 }
// To arrayconst arr = pos.toArray(); // [100.5, 200.5]
// Get raw values (for network)const raw = pos.toRawObject(); // { x: 6586368, y: 13140992 }FixedMath Trigonometric Functions
Section titled “FixedMath Trigonometric Functions”Deterministic trigonometric functions using lookup tables.
import { FixedMath, Fixed32 } from '@esengine/ecs-framework-math';
const angle = Fixed32.from(Math.PI / 6); // 30 degrees
// Trigonometric functionsconst sin = FixedMath.sin(angle); // 0.5const cos = FixedMath.cos(angle); // 0.866const tan = FixedMath.tan(angle); // 0.577
// Inverse trigonometricconst atan = FixedMath.atan2(y, x);const asin = FixedMath.asin(value);const acos = FixedMath.acos(value);
// Normalize angle to [-π, π]const normalized = FixedMath.normalizeAngle(angle);
// Angle difference (shortest path)const delta = FixedMath.angleDelta(from, to);
// Angle interpolation (handles 360° wrap)const lerped = FixedMath.lerpAngle(from, to, t);
// Radian/degree conversionconst deg = FixedMath.radToDeg(rad);const rad = FixedMath.degToRad(deg);Best Practices
Section titled “Best Practices”1. Use Fixed-Point Throughout
Section titled “1. Use Fixed-Point Throughout”// ✅ Correct: all game logic uses fixed-pointfunction calculateDamage(baseDamage: Fixed32, multiplier: Fixed32): Fixed32 { return baseDamage.mul(multiplier);}
// ❌ Wrong: mixing floating-pointfunction calculateDamage(baseDamage: number, multiplier: number): number { return baseDamage * multiplier; // may be inconsistent}2. Only Convert to Float for Rendering
Section titled “2. Only Convert to Float for Rendering”// Game logicconst position: FixedVector2 = calculatePosition(input);
// Renderingconst { x, y } = position.toObject();sprite.position.set(x, y);3. Use Raw Values for Network
Section titled “3. Use Raw Values for Network”// ✅ Correct: transmit raw integersconst raw = position.toRawObject();send(JSON.stringify(raw));
// ❌ Wrong: transmit floatsconst float = position.toObject();send(JSON.stringify(float)); // may lose precision4. Use FixedMath for Trigonometry
Section titled “4. Use FixedMath for Trigonometry”// ✅ Correct: use lookup tablesconst direction = FixedVector2.fromAngle(FixedMath.atan2(dy, dx));
// ❌ Wrong: use Math libraryconst angle = Math.atan2(dy.toNumber(), dx.toNumber()); // non-deterministicAPI Exports
Section titled “API Exports”import { Fixed32, FixedVector2, FixedMath, type IFixed32, type IFixedVector2} from '@esengine/ecs-framework-math';Related Docs
Section titled “Related Docs”- State Sync - Fixed-point snapshot buffer
- Client Prediction - Fixed-point client prediction