murow 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/core/binary-codec/binary-codec.d.ts +159 -0
- package/dist/core/binary-codec/binary-codec.js +336 -0
- package/dist/core/binary-codec/index.d.ts +1 -0
- package/dist/core/binary-codec/index.js +1 -0
- package/dist/core/events/event-system.d.ts +71 -0
- package/dist/core/events/event-system.js +88 -0
- package/dist/core/events/index.d.ts +1 -0
- package/dist/core/events/index.js +1 -0
- package/dist/core/fixed-ticker/fixed-ticker.d.ts +105 -0
- package/dist/core/fixed-ticker/fixed-ticker.js +91 -0
- package/dist/core/fixed-ticker/index.d.ts +1 -0
- package/dist/core/fixed-ticker/index.js +1 -0
- package/dist/core/generate-id/generate-id.d.ts +21 -0
- package/dist/core/generate-id/generate-id.js +25 -0
- package/dist/core/generate-id/index.d.ts +1 -0
- package/dist/core/generate-id/index.js +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +8 -0
- package/dist/core/lerp/index.d.ts +1 -0
- package/dist/core/lerp/index.js +1 -0
- package/dist/core/lerp/lerp.d.ts +40 -0
- package/dist/core/lerp/lerp.js +42 -0
- package/dist/core/navmesh/index.d.ts +1 -0
- package/dist/core/navmesh/index.js +1 -0
- package/dist/core/navmesh/navmesh.d.ts +116 -0
- package/dist/core/navmesh/navmesh.js +666 -0
- package/dist/core/pooled-codec/index.d.ts +1 -0
- package/dist/core/pooled-codec/index.js +1 -0
- package/dist/core/pooled-codec/pooled-codec.d.ts +140 -0
- package/dist/core/pooled-codec/pooled-codec.js +213 -0
- package/dist/core/prediction/index.d.ts +1 -0
- package/dist/core/prediction/index.js +1 -0
- package/dist/core/prediction/prediction.d.ts +64 -0
- package/dist/core/prediction/prediction.js +90 -0
- package/dist/core.esm.js +1 -0
- package/dist/core.js +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +18 -0
- package/dist/protocol/index.d.ts +43 -0
- package/dist/protocol/index.js +43 -0
- package/dist/protocol/intent/index.d.ts +39 -0
- package/dist/protocol/intent/index.js +38 -0
- package/dist/protocol/intent/intent-registry.d.ts +54 -0
- package/dist/protocol/intent/intent-registry.js +73 -0
- package/dist/protocol/intent/intent.d.ts +12 -0
- package/dist/protocol/intent/intent.js +1 -0
- package/dist/protocol/snapshot/index.d.ts +44 -0
- package/dist/protocol/snapshot/index.js +43 -0
- package/dist/protocol/snapshot/snapshot-codec.d.ts +48 -0
- package/dist/protocol/snapshot/snapshot-codec.js +56 -0
- package/dist/protocol/snapshot/snapshot-registry.d.ts +100 -0
- package/dist/protocol/snapshot/snapshot-registry.js +136 -0
- package/dist/protocol/snapshot/snapshot.d.ts +19 -0
- package/dist/protocol/snapshot/snapshot.js +30 -0
- package/package.json +54 -0
- package/src/core/binary-codec/README.md +60 -0
- package/src/core/binary-codec/binary-codec.test.ts +300 -0
- package/src/core/binary-codec/binary-codec.ts +430 -0
- package/src/core/binary-codec/index.ts +1 -0
- package/src/core/events/README.md +47 -0
- package/src/core/events/event-system.test.ts +243 -0
- package/src/core/events/event-system.ts +140 -0
- package/src/core/events/index.ts +1 -0
- package/src/core/fixed-ticker/README.md +77 -0
- package/src/core/fixed-ticker/fixed-ticker.test.ts +151 -0
- package/src/core/fixed-ticker/fixed-ticker.ts +158 -0
- package/src/core/fixed-ticker/index.ts +1 -0
- package/src/core/generate-id/README.md +18 -0
- package/src/core/generate-id/generate-id.test.ts +79 -0
- package/src/core/generate-id/generate-id.ts +37 -0
- package/src/core/generate-id/index.ts +1 -0
- package/src/core/index.ts +8 -0
- package/src/core/lerp/README.md +79 -0
- package/src/core/lerp/index.ts +1 -0
- package/src/core/lerp/lerp.test.ts +90 -0
- package/src/core/lerp/lerp.ts +42 -0
- package/src/core/navmesh/README.md +124 -0
- package/src/core/navmesh/index.ts +1 -0
- package/src/core/navmesh/navmesh.test.ts +344 -0
- package/src/core/navmesh/navmesh.ts +850 -0
- package/src/core/pooled-codec/README.md +70 -0
- package/src/core/pooled-codec/index.ts +1 -0
- package/src/core/pooled-codec/pooled-codec.test.ts +349 -0
- package/src/core/pooled-codec/pooled-codec.ts +239 -0
- package/src/core/prediction/README.md +64 -0
- package/src/core/prediction/index.ts +1 -0
- package/src/core/prediction/prediction.test.ts +422 -0
- package/src/core/prediction/prediction.ts +101 -0
- package/src/index.ts +20 -0
- package/src/protocol/README.md +310 -0
- package/src/protocol/index.ts +44 -0
- package/src/protocol/intent/index.ts +40 -0
- package/src/protocol/intent/intent-registry.test.ts +237 -0
- package/src/protocol/intent/intent-registry.ts +88 -0
- package/src/protocol/intent/intent.ts +12 -0
- package/src/protocol/snapshot/index.ts +45 -0
- package/src/protocol/snapshot/snapshot-codec.test.ts +138 -0
- package/src/protocol/snapshot/snapshot-codec.ts +71 -0
- package/src/protocol/snapshot/snapshot-registry.test.ts +302 -0
- package/src/protocol/snapshot/snapshot-registry.ts +162 -0
- package/src/protocol/snapshot/snapshot.test.ts +76 -0
- package/src/protocol/snapshot/snapshot.ts +41 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description
|
|
3
|
+
* A utility class for managing fixed-rate update ticks, useful for deterministic behaviour
|
|
4
|
+
* in both client and server.
|
|
5
|
+
*
|
|
6
|
+
* The `FixedTicker` accumulates elapsed time and determines how many fixed-interval "ticks"
|
|
7
|
+
* should be processed based on a specified tick rate. It also limits the maximum number of
|
|
8
|
+
* ticks per frame to prevent runaway update loops when frame times are long.
|
|
9
|
+
*
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* - The `tick` method should be called once per frame, passing the elapsed time in seconds.
|
|
13
|
+
* - The class ensures that no more than a safe number of ticks are processed per frame.
|
|
14
|
+
*/
|
|
15
|
+
export class FixedTicker {
|
|
16
|
+
/**
|
|
17
|
+
* @description Accumulator for the time passed since the last tick
|
|
18
|
+
*/
|
|
19
|
+
private accumulator = 0;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @description Rate of ticks per second
|
|
23
|
+
*/
|
|
24
|
+
rate: number;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @description
|
|
28
|
+
* Interval in milliseconds per tick
|
|
29
|
+
*/
|
|
30
|
+
private intervalMs: number;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @description
|
|
34
|
+
* Maximum amount of ticks to run per frame, to avoid
|
|
35
|
+
* running too many ticks in a single frame.
|
|
36
|
+
*/
|
|
37
|
+
private maxTicksPerFrame: number;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @description
|
|
41
|
+
* Callback to execute on each tick
|
|
42
|
+
*/
|
|
43
|
+
private onTick: (deltaTime: number, tick?: number) => void;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @description
|
|
47
|
+
* Optional callback to execute when ticks are skipped due to high delta time.
|
|
48
|
+
* This can be useful for debugging or logging purposes.
|
|
49
|
+
*/
|
|
50
|
+
private onTickSkipped?: (skippedTicks: number) => void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @description
|
|
54
|
+
* Internal counter for the number of ticks processed.
|
|
55
|
+
*/
|
|
56
|
+
private _tickCount = 0;
|
|
57
|
+
|
|
58
|
+
constructor({ rate, onTick }: FixedTickerProps) {
|
|
59
|
+
this.rate = rate;
|
|
60
|
+
this.intervalMs = 1000 / this.rate;
|
|
61
|
+
this.onTick = onTick;
|
|
62
|
+
|
|
63
|
+
// Allow up to rate/2 ticks per frame, but at least 1.
|
|
64
|
+
// Prevents runaway loops if delta is too large.
|
|
65
|
+
this.maxTicksPerFrame = Math.max(1, Math.floor(rate / 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @description
|
|
70
|
+
* Returns how many ticks to run.
|
|
71
|
+
*
|
|
72
|
+
* @param deltaTime Delta time in seconds
|
|
73
|
+
* @returns {number} Amount of ticks to run
|
|
74
|
+
*/
|
|
75
|
+
private getTicks(deltaTime: number): number {
|
|
76
|
+
this.accumulator += deltaTime * 1000;
|
|
77
|
+
|
|
78
|
+
let ticks = 0;
|
|
79
|
+
|
|
80
|
+
while (
|
|
81
|
+
this.accumulator >= this.intervalMs &&
|
|
82
|
+
ticks < this.maxTicksPerFrame
|
|
83
|
+
) {
|
|
84
|
+
this.accumulator -= this.intervalMs;
|
|
85
|
+
ticks++;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const skippedTicks = Math.floor(this.accumulator / this.intervalMs);
|
|
89
|
+
if (skippedTicks > 0 && this.onTickSkipped) {
|
|
90
|
+
this.onTickSkipped(skippedTicks);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return ticks;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @description
|
|
98
|
+
* Processes the ticks based on the elapsed time.
|
|
99
|
+
*
|
|
100
|
+
* @param deltaTime Delta time in seconds
|
|
101
|
+
*/
|
|
102
|
+
tick(deltaTime: number): void {
|
|
103
|
+
const ticks = this.getTicks(deltaTime);
|
|
104
|
+
|
|
105
|
+
for (let i = 0; i < ticks; i++) {
|
|
106
|
+
this.onTick(1 / this.rate, this._tickCount++);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @description
|
|
112
|
+
* Returns the number of ticks processed since the last reset.
|
|
113
|
+
*
|
|
114
|
+
* @returns {number} Number of ticks processed
|
|
115
|
+
*/
|
|
116
|
+
get tickCount(): number {
|
|
117
|
+
return this._tickCount;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @description
|
|
122
|
+
* Resets the tick count to zero.
|
|
123
|
+
*/
|
|
124
|
+
resetTickCount(): void {
|
|
125
|
+
this._tickCount = 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @description
|
|
130
|
+
* Returns the accumulated time in seconds, useful for interpolation.
|
|
131
|
+
*
|
|
132
|
+
* @returns {number} Accumulated time in seconds
|
|
133
|
+
*/
|
|
134
|
+
get accumulatedTime(): number {
|
|
135
|
+
return this.accumulator / 1000; // Convert to seconds
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
interface FixedTickerProps {
|
|
140
|
+
/**
|
|
141
|
+
* @description
|
|
142
|
+
* Rate of ticks per second
|
|
143
|
+
*/
|
|
144
|
+
rate: number;
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @description
|
|
148
|
+
* Callback to execute on each tick
|
|
149
|
+
*/
|
|
150
|
+
onTick: (deltaTime: number, tick?: number) => void;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @description
|
|
154
|
+
* Optional callback to execute when ticks are skipped due to high delta time.
|
|
155
|
+
* This can be useful for debugging or logging purposes.
|
|
156
|
+
*/
|
|
157
|
+
onTickSkipped?: (skippedTicks: number) => void
|
|
158
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './fixed-ticker';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# generateId
|
|
2
|
+
|
|
3
|
+
A simple utility function to generate unique 64-bit identifiers using the Web Crypto API.
|
|
4
|
+
The IDs are returned as hexadecimal strings and work in both modern browsers and Node.js (v15+).
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Generates cryptographically strong 64-bit IDs.
|
|
9
|
+
- Returns hexadecimal strings (always padded to 16 characters).
|
|
10
|
+
- Works in browsers and Node ≥15 without any imports.
|
|
11
|
+
- Fast and clean using BigInt arithmetic.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { generateId } from './generate-id';
|
|
17
|
+
const id = generateId(); // akwats 16-character hex string
|
|
18
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { generateId } from "./generate-id";
|
|
3
|
+
|
|
4
|
+
describe("generateId", () => {
|
|
5
|
+
test("should generate an ID with default length of 16", () => {
|
|
6
|
+
const id = generateId();
|
|
7
|
+
expect(id.length).toBe(16);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("should generate a hexadecimal string", () => {
|
|
11
|
+
const id = generateId();
|
|
12
|
+
expect(id).toMatch(/^[0-9a-f]+$/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("should generate unique IDs", () => {
|
|
16
|
+
const ids = new Set();
|
|
17
|
+
for (let i = 0; i < 1000; i++) {
|
|
18
|
+
ids.add(generateId());
|
|
19
|
+
}
|
|
20
|
+
expect(ids.size).toBe(1000);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should include prefix when provided", () => {
|
|
24
|
+
const prefix = "user_";
|
|
25
|
+
const id = generateId({ prefix });
|
|
26
|
+
expect(id.startsWith(prefix)).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should maintain total size including prefix", () => {
|
|
30
|
+
const prefix = "user_";
|
|
31
|
+
const size = 20;
|
|
32
|
+
const id = generateId({ prefix, size });
|
|
33
|
+
expect(id.length).toBe(size);
|
|
34
|
+
expect(id.startsWith(prefix)).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("should generate custom size without prefix", () => {
|
|
38
|
+
const size = 32;
|
|
39
|
+
const id = generateId({ size });
|
|
40
|
+
expect(id.length).toBe(size);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("should enforce minimum of 8 hex characters", () => {
|
|
44
|
+
const prefix = "verylongprefix_";
|
|
45
|
+
const size = 10; // shorter than prefix + 8
|
|
46
|
+
const id = generateId({ prefix, size });
|
|
47
|
+
// Should be prefix + at least 8 hex chars
|
|
48
|
+
expect(id.length).toBeGreaterThanOrEqual(prefix.length + 8);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should pad ID to desired length", () => {
|
|
52
|
+
const size = 24;
|
|
53
|
+
const id = generateId({ size });
|
|
54
|
+
expect(id.length).toBe(size);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("should generate different IDs on consecutive calls", () => {
|
|
58
|
+
const id1 = generateId();
|
|
59
|
+
const id2 = generateId();
|
|
60
|
+
expect(id1).not.toBe(id2);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("should work with various prefix lengths", () => {
|
|
64
|
+
const prefixes = ["a", "ab", "abc", "player_", "super_long_prefix_"];
|
|
65
|
+
prefixes.forEach((prefix) => {
|
|
66
|
+
const id = generateId({ prefix });
|
|
67
|
+
expect(id.startsWith(prefix)).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should generate valid hex with leading zeros preserved", () => {
|
|
72
|
+
// Generate many IDs to ensure padding works correctly
|
|
73
|
+
for (let i = 0; i < 100; i++) {
|
|
74
|
+
const id = generateId({ size: 16 });
|
|
75
|
+
expect(id.length).toBe(16);
|
|
76
|
+
expect(id).toMatch(/^[0-9a-f]{16}$/);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
interface GenerateIdOptions {
|
|
2
|
+
/** Optional prefix to prepend to the ID */
|
|
3
|
+
prefix?: string;
|
|
4
|
+
/** Total length of the returned ID including prefix (default 16) */
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @description
|
|
10
|
+
* Generates a unique identifier as a hexadecimal string.
|
|
11
|
+
* Can include a prefix and a custom total length.
|
|
12
|
+
*
|
|
13
|
+
* @param options Optional configuration: prefix and total size
|
|
14
|
+
* @returns A unique identifier string
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* generateId(); // "f3a2b1c4d5e67890"
|
|
18
|
+
* generateId({ prefix: 'user_' }); // "user_f3a2b1c4d5e67890"
|
|
19
|
+
* generateId({ prefix: 'user_', size: 24 }); // "user_00f3a2b1c4d5e67890"
|
|
20
|
+
*/
|
|
21
|
+
export function generateId(options: GenerateIdOptions = {}): string {
|
|
22
|
+
const { prefix = "", size = 16 } = options;
|
|
23
|
+
|
|
24
|
+
// compute number of hex characters to generate (subtract prefix length)
|
|
25
|
+
const hexLength = Math.max(size - prefix.length, 8); // min 8 hex chars
|
|
26
|
+
|
|
27
|
+
// number of 32-bit integers needed to cover the hexLength
|
|
28
|
+
const numInts = Math.ceil(hexLength / 8);
|
|
29
|
+
|
|
30
|
+
const arr = crypto.getRandomValues(new Uint32Array(numInts));
|
|
31
|
+
let id = arr.reduce((acc, val) => acc + val.toString(16).padStart(8, "0"), "");
|
|
32
|
+
|
|
33
|
+
// truncate/pad to desired length
|
|
34
|
+
id = id.slice(0, hexLength).padStart(hexLength, "0");
|
|
35
|
+
|
|
36
|
+
return `${prefix}${id}`;
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './generate-id';
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# lerp
|
|
2
|
+
|
|
3
|
+
A simple utility function for linear interpolation between two numeric values. Perfect for smooth animations, transitions, and value easing in game development.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Clean linear interpolation implementation.
|
|
8
|
+
- Unclamped by design - allows extrapolation when t is outside [0, 1].
|
|
9
|
+
- Zero dependencies.
|
|
10
|
+
- Works in browsers and Node.js.
|
|
11
|
+
- TypeScript support with proper type definitions.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { lerp } from './lerp';
|
|
17
|
+
|
|
18
|
+
// Basic interpolation
|
|
19
|
+
const value = lerp(0, 100, 0.5); // Returns 50
|
|
20
|
+
|
|
21
|
+
// Animation example
|
|
22
|
+
const startPos = 0;
|
|
23
|
+
const endPos = 100;
|
|
24
|
+
const progress = 0.75;
|
|
25
|
+
const currentPos = lerp(startPos, endPos, progress); // Returns 75
|
|
26
|
+
|
|
27
|
+
// Smooth camera movement
|
|
28
|
+
function updateCamera(deltaTime: number) {
|
|
29
|
+
const t = deltaTime * smoothingFactor;
|
|
30
|
+
camera.x = lerp(camera.x, target.x, t);
|
|
31
|
+
camera.y = lerp(camera.y, target.y, t);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Color transitions
|
|
35
|
+
const r = lerp(startColor.r, endColor.r, progress);
|
|
36
|
+
const g = lerp(startColor.g, endColor.g, progress);
|
|
37
|
+
const b = lerp(startColor.b, endColor.b, progress);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Parameters
|
|
41
|
+
|
|
42
|
+
- `start` (number): The starting value (returned when t = 0)
|
|
43
|
+
- `end` (number): The ending value (returned when t = 1)
|
|
44
|
+
- `t` (number): The interpolation factor, typically in range [0, 1]
|
|
45
|
+
|
|
46
|
+
## Returns
|
|
47
|
+
|
|
48
|
+
`number` - The interpolated value between start and end.
|
|
49
|
+
|
|
50
|
+
## Extrapolation
|
|
51
|
+
|
|
52
|
+
The function does not clamp the `t` parameter, allowing extrapolation:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
lerp(0, 100, 1.5); // Returns 150 (extrapolated beyond end)
|
|
56
|
+
lerp(0, 100, -0.5); // Returns -50 (extrapolated before start)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If you need clamped interpolation, combine with a clamp function:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
function clampedLerp(start: number, end: number, t: number): number {
|
|
63
|
+
const clampedT = Math.max(0, Math.min(1, t));
|
|
64
|
+
return lerp(start, end, clampedT);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Common Use Cases
|
|
69
|
+
|
|
70
|
+
- **Smooth animations**: Interpolate position, rotation, scale over time
|
|
71
|
+
- **Camera movement**: Create smooth camera following behavior
|
|
72
|
+
- **UI transitions**: Fade effects, sliding panels, progress bars
|
|
73
|
+
- **Color blending**: Transition between colors smoothly
|
|
74
|
+
- **Value easing**: Gradually approach target values
|
|
75
|
+
- **Physics simulations**: Interpolate between physics states for rendering
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
`lerp` provides a foundational building block for smooth, continuous value transitions in game development and interactive applications.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './lerp';
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { lerp } from "./lerp";
|
|
3
|
+
|
|
4
|
+
describe("lerp", () => {
|
|
5
|
+
test("should return start value when t is 0", () => {
|
|
6
|
+
expect(lerp(0, 100, 0)).toBe(0);
|
|
7
|
+
expect(lerp(50, 200, 0)).toBe(50);
|
|
8
|
+
expect(lerp(-10, 10, 0)).toBe(-10);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("should return end value when t is 1", () => {
|
|
12
|
+
expect(lerp(0, 100, 1)).toBe(100);
|
|
13
|
+
expect(lerp(50, 200, 1)).toBe(200);
|
|
14
|
+
expect(lerp(-10, 10, 1)).toBe(10);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("should return midpoint when t is 0.5", () => {
|
|
18
|
+
expect(lerp(0, 100, 0.5)).toBe(50);
|
|
19
|
+
expect(lerp(50, 150, 0.5)).toBe(100);
|
|
20
|
+
expect(lerp(-10, 10, 0.5)).toBe(0);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should interpolate between positive values", () => {
|
|
24
|
+
expect(lerp(0, 100, 0.25)).toBe(25);
|
|
25
|
+
expect(lerp(0, 100, 0.75)).toBe(75);
|
|
26
|
+
expect(lerp(10, 20, 0.3)).toBeCloseTo(13, 5);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("should interpolate between negative values", () => {
|
|
30
|
+
expect(lerp(-100, -50, 0.5)).toBe(-75);
|
|
31
|
+
expect(lerp(-10, -5, 0.2)).toBe(-9);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("should interpolate from negative to positive", () => {
|
|
35
|
+
expect(lerp(-50, 50, 0.5)).toBe(0);
|
|
36
|
+
expect(lerp(-10, 10, 0.25)).toBe(-5);
|
|
37
|
+
expect(lerp(-10, 10, 0.75)).toBe(5);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("should handle extrapolation when t > 1", () => {
|
|
41
|
+
expect(lerp(0, 100, 1.5)).toBe(150);
|
|
42
|
+
expect(lerp(0, 100, 2)).toBe(200);
|
|
43
|
+
expect(lerp(10, 20, 1.1)).toBeCloseTo(21, 5);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("should handle extrapolation when t < 0", () => {
|
|
47
|
+
expect(lerp(0, 100, -0.5)).toBe(-50);
|
|
48
|
+
expect(lerp(0, 100, -1)).toBe(-100);
|
|
49
|
+
expect(lerp(10, 20, -0.1)).toBeCloseTo(9, 5);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("should handle same start and end values", () => {
|
|
53
|
+
expect(lerp(50, 50, 0)).toBe(50);
|
|
54
|
+
expect(lerp(50, 50, 0.5)).toBe(50);
|
|
55
|
+
expect(lerp(50, 50, 1)).toBe(50);
|
|
56
|
+
expect(lerp(50, 50, 2)).toBe(50);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("should handle floating point values", () => {
|
|
60
|
+
expect(lerp(0.1, 0.9, 0.5)).toBeCloseTo(0.5, 10);
|
|
61
|
+
expect(lerp(1.5, 2.5, 0.3)).toBeCloseTo(1.8, 10);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("should be commutative with inverted t", () => {
|
|
65
|
+
const start = 10;
|
|
66
|
+
const end = 20;
|
|
67
|
+
expect(lerp(start, end, 0.3)).toBeCloseTo(lerp(end, start, 0.7), 10);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("should handle very small differences", () => {
|
|
71
|
+
expect(lerp(1.0, 1.0001, 0.5)).toBeCloseTo(1.00005, 10);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("should handle very large values", () => {
|
|
75
|
+
expect(lerp(1e10, 2e10, 0.5)).toBe(1.5e10);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("should be linear (no easing)", () => {
|
|
79
|
+
const start = 0;
|
|
80
|
+
const end = 100;
|
|
81
|
+
const step = 0.1;
|
|
82
|
+
const expectedDiff = 10;
|
|
83
|
+
|
|
84
|
+
for (let t = 0; t < 1; t += step) {
|
|
85
|
+
const val1 = lerp(start, end, t);
|
|
86
|
+
const val2 = lerp(start, end, t + step);
|
|
87
|
+
expect(val2 - val1).toBeCloseTo(expectedDiff, 5);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @description
|
|
3
|
+
* Performs linear interpolation between two values.
|
|
4
|
+
*
|
|
5
|
+
* Linear interpolation (lerp) calculates a value between a start and end point
|
|
6
|
+
* based on a normalized interpolation factor (t). When t = 0, the result equals
|
|
7
|
+
* the start value; when t = 1, the result equals the end value. Values of t
|
|
8
|
+
* between 0 and 1 produce intermediate results.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* - This function does not clamp the interpolation factor. Values of t outside
|
|
12
|
+
* the range [0, 1] will extrapolate beyond the start and end values.
|
|
13
|
+
* - For clamped interpolation, combine with a clamp function on the t parameter.
|
|
14
|
+
* - Commonly used for smooth animations, camera movements, and value transitions.
|
|
15
|
+
*
|
|
16
|
+
* @param start - The starting value (when t = 0)
|
|
17
|
+
* @param end - The ending value (when t = 1)
|
|
18
|
+
* @param t - The interpolation factor, typically in the range [0, 1]
|
|
19
|
+
*
|
|
20
|
+
* @returns {number} The interpolated value between start and end
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* // Basic interpolation
|
|
25
|
+
* lerp(0, 100, 0.5); // Returns 50
|
|
26
|
+
* lerp(0, 100, 0); // Returns 0
|
|
27
|
+
* lerp(0, 100, 1); // Returns 100
|
|
28
|
+
*
|
|
29
|
+
* // Animation example
|
|
30
|
+
* const startPos = 0;
|
|
31
|
+
* const endPos = 100;
|
|
32
|
+
* const progress = 0.75;
|
|
33
|
+
* const currentPos = lerp(startPos, endPos, progress); // Returns 75
|
|
34
|
+
*
|
|
35
|
+
* // Extrapolation (t outside [0, 1])
|
|
36
|
+
* lerp(0, 100, 1.5); // Returns 150
|
|
37
|
+
* lerp(0, 100, -0.5); // Returns -50
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function lerp(start: number, end: number, t: number): number {
|
|
41
|
+
return start + (end - start) * t;
|
|
42
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# NavMesh / Pathfinding Utility
|
|
2
|
+
|
|
3
|
+
A lightweight navigation system for grid-based and hybrid games.
|
|
4
|
+
|
|
5
|
+
Supports:
|
|
6
|
+
|
|
7
|
+
* **Grid A*** pathfinding
|
|
8
|
+
* **Line-of-sight graph navigation**
|
|
9
|
+
* **Dynamic obstacles**
|
|
10
|
+
* **Spatial hashing for fast queries**
|
|
11
|
+
* **Circle / Rect / Polygon obstacles**
|
|
12
|
+
* **Zero rebuilds unless data changes**
|
|
13
|
+
|
|
14
|
+
Designed for **games**, not CAD-grade geometry.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
* ⚡ **Fast obstacle queries** via spatial hash
|
|
21
|
+
* 🧠 **Smart rebuilds** (version-based, no unnecessary work)
|
|
22
|
+
* 🧩 **Multiple obstacle types**
|
|
23
|
+
* 🧭 **A*** with binary heap
|
|
24
|
+
* 🧱 **Grid or graph navigation**
|
|
25
|
+
* 🔁 Dynamic obstacle add / move / remove
|
|
26
|
+
* 🧪 Deterministic & allocation-safe
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Create navmesh
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
const nav = new NavMesh('grid'); // or 'graph'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Add obstacles
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
nav.addObstacle({
|
|
42
|
+
type: 'circle',
|
|
43
|
+
pos: { x: 5, y: 5 },
|
|
44
|
+
radius: 2
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
nav.addObstacle({
|
|
50
|
+
type: 'rect',
|
|
51
|
+
pos: { x: 2, y: 3 },
|
|
52
|
+
size: { x: 4, y: 2 },
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
nav.addObstacle({
|
|
58
|
+
type: 'polygon',
|
|
59
|
+
pos: { x: 10, y: 5 },
|
|
60
|
+
points: [
|
|
61
|
+
{ x: 0, y: 0 },
|
|
62
|
+
{ x: 2, y: 0 },
|
|
63
|
+
{ x: 1, y: 2 },
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Move / remove
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
nav.moveObstacle(id, { x: 8, y: 4 });
|
|
72
|
+
nav.removeObstacle(id);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Find path
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const path = nav.findPath({
|
|
79
|
+
from: { x: 1, y: 1 },
|
|
80
|
+
to: { x: 10, y: 8 }
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Navigation Modes
|
|
87
|
+
|
|
88
|
+
### `grid`
|
|
89
|
+
|
|
90
|
+
* A* over grid cells
|
|
91
|
+
* Accurate
|
|
92
|
+
* Best for RTS / tactics / tile games
|
|
93
|
+
|
|
94
|
+
### `graph`
|
|
95
|
+
|
|
96
|
+
* Line-of-sight check
|
|
97
|
+
* Falls back to grid if blocked
|
|
98
|
+
* Faster for open maps
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Performance
|
|
103
|
+
|
|
104
|
+
| Feature | Cost |
|
|
105
|
+
| -------------- | --------------------------------- |
|
|
106
|
+
| Obstacle query | **O(1)** avg |
|
|
107
|
+
| Grid rebuild | O(n × area) |
|
|
108
|
+
| Pathfinding | O(bᵈ log n) |
|
|
109
|
+
| Memory | Minimal, no allocations per frame |
|
|
110
|
+
|
|
111
|
+
Handles:
|
|
112
|
+
|
|
113
|
+
* 1k+ obstacles
|
|
114
|
+
* 10k+ A* nodes
|
|
115
|
+
* Real-time updates
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
* Polygon points **must be local (0,0-based)**
|
|
122
|
+
* Rotation is supported for rects & polygons
|
|
123
|
+
* All math is deterministic
|
|
124
|
+
* No dependencies
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './navmesh';
|