gridstamp 1.0.0

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.
Files changed (42) hide show
  1. package/.cursorrules +74 -0
  2. package/CLAUDE.md +61 -0
  3. package/LICENSE +190 -0
  4. package/README.md +107 -0
  5. package/dist/index.js +194 -0
  6. package/package.json +84 -0
  7. package/src/antispoofing/detector.ts +509 -0
  8. package/src/antispoofing/index.ts +7 -0
  9. package/src/gamification/badges.ts +429 -0
  10. package/src/gamification/fleet-leaderboard.ts +293 -0
  11. package/src/gamification/index.ts +44 -0
  12. package/src/gamification/streaks.ts +243 -0
  13. package/src/gamification/trust-tiers.ts +393 -0
  14. package/src/gamification/zone-mastery.ts +256 -0
  15. package/src/index.ts +341 -0
  16. package/src/memory/index.ts +9 -0
  17. package/src/memory/place-cells.ts +279 -0
  18. package/src/memory/spatial-memory.ts +375 -0
  19. package/src/navigation/index.ts +1 -0
  20. package/src/navigation/pathfinding.ts +403 -0
  21. package/src/perception/camera.ts +249 -0
  22. package/src/perception/index.ts +2 -0
  23. package/src/types/index.ts +416 -0
  24. package/src/utils/crypto.ts +94 -0
  25. package/src/utils/index.ts +2 -0
  26. package/src/utils/math.ts +204 -0
  27. package/src/verification/index.ts +9 -0
  28. package/src/verification/spatial-proof.ts +442 -0
  29. package/tests/antispoofing/detector.test.ts +196 -0
  30. package/tests/gamification/badges.test.ts +163 -0
  31. package/tests/gamification/fleet-leaderboard.test.ts +181 -0
  32. package/tests/gamification/streaks.test.ts +158 -0
  33. package/tests/gamification/trust-tiers.test.ts +165 -0
  34. package/tests/gamification/zone-mastery.test.ts +143 -0
  35. package/tests/memory/place-cells.test.ts +128 -0
  36. package/tests/stress/load.test.ts +499 -0
  37. package/tests/stress/security.test.ts +378 -0
  38. package/tests/stress/simulation.test.ts +361 -0
  39. package/tests/utils/crypto.test.ts +115 -0
  40. package/tests/utils/math.test.ts +195 -0
  41. package/tests/verification/spatial-proof.test.ts +299 -0
  42. package/tsconfig.json +26 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * 3-Tier Spatial Memory System (Bio-inspired)
3
+ *
4
+ * Short-term: 30Hz live perception buffer (~30s window, 1M splats max)
5
+ * Mid-term: Episodic memories with semantic tags (~hours, 100K splats)
6
+ * Long-term: Merkle-signed persistent room maps (~forever, 10K splats/room)
7
+ *
8
+ * Consolidation: short → mid (similarity clustering) → long (Merkle signing)
9
+ * Eviction: LRU for short, confidence-weighted for mid, never for long
10
+ */
11
+ import { MerkleTree } from 'merkletreejs';
12
+ import type {
13
+ ShortTermEntry,
14
+ EpisodicMemory,
15
+ LongTermMemory as LTMEntry,
16
+ ConsolidationEvent,
17
+ MemoryTier,
18
+ GaussianSplat,
19
+ SplatScene,
20
+ CameraFrame,
21
+ Vec3,
22
+ AABB,
23
+ } from '../types/index.js';
24
+ import { sha256, hmacSign, generateNonce } from '../utils/crypto.js';
25
+ import { vec3Distance } from '../utils/math.js';
26
+
27
+ // ============================================================
28
+ // SHORT-TERM MEMORY (live perception buffer)
29
+ // ============================================================
30
+
31
+ export class ShortTermMemory {
32
+ private entries: ShortTermEntry[] = [];
33
+ private readonly maxEntries: number;
34
+ private readonly ttlMs: number;
35
+
36
+ constructor(maxEntries: number = 900, ttlMs: number = 30_000) {
37
+ this.maxEntries = maxEntries; // ~30s at 30fps
38
+ this.ttlMs = ttlMs;
39
+ }
40
+
41
+ /** Add frame + splats from current perception */
42
+ add(frame: CameraFrame, splats: GaussianSplat[]): void {
43
+ const now = Date.now();
44
+ this.entries.push({
45
+ frame,
46
+ splats,
47
+ timestamp: now,
48
+ expiresAt: now + this.ttlMs,
49
+ });
50
+ this.evict();
51
+ }
52
+
53
+ /** Evict expired entries and enforce max capacity */
54
+ private evict(): void {
55
+ const now = Date.now();
56
+ this.entries = this.entries.filter(e => e.expiresAt > now);
57
+ if (this.entries.length > this.maxEntries) {
58
+ this.entries = this.entries.slice(-this.maxEntries);
59
+ }
60
+ }
61
+
62
+ /** Get all current entries */
63
+ getAll(): readonly ShortTermEntry[] {
64
+ this.evict();
65
+ return [...this.entries];
66
+ }
67
+
68
+ /** Get total splat count across all entries */
69
+ getSplatCount(): number {
70
+ return this.entries.reduce((sum, e) => sum + e.splats.length, 0);
71
+ }
72
+
73
+ /** Get most recent N entries */
74
+ getRecent(n: number): readonly ShortTermEntry[] {
75
+ this.evict();
76
+ return this.entries.slice(-n);
77
+ }
78
+
79
+ /** Clear all short-term memory */
80
+ clear(): void {
81
+ this.entries = [];
82
+ }
83
+
84
+ get count(): number {
85
+ this.evict();
86
+ return this.entries.length;
87
+ }
88
+ }
89
+
90
+ // ============================================================
91
+ // MID-TERM EPISODIC MEMORY
92
+ // ============================================================
93
+
94
+ export class MidTermMemory {
95
+ private memories: Map<string, EpisodicMemory> = new Map();
96
+ private readonly maxEntries: number;
97
+
98
+ constructor(maxEntries: number = 1000) {
99
+ this.maxEntries = maxEntries;
100
+ }
101
+
102
+ /** Store an episodic memory from consolidated short-term entries */
103
+ store(scene: SplatScene, location: Vec3, tags: string[] = []): EpisodicMemory {
104
+ const memory: EpisodicMemory = {
105
+ id: generateNonce(16),
106
+ scene,
107
+ location,
108
+ timestamp: Date.now(),
109
+ tags,
110
+ confidence: 1.0,
111
+ };
112
+ this.memories.set(memory.id, memory);
113
+ this.enforceCapacity();
114
+ return memory;
115
+ }
116
+
117
+ /** Find memories near a location */
118
+ findNear(location: Vec3, radiusMeters: number): readonly EpisodicMemory[] {
119
+ const results: EpisodicMemory[] = [];
120
+ for (const memory of this.memories.values()) {
121
+ if (vec3Distance(memory.location, location) <= radiusMeters) {
122
+ results.push(memory);
123
+ }
124
+ }
125
+ return results.sort((a, b) => b.timestamp - a.timestamp);
126
+ }
127
+
128
+ /** Find memories by semantic tag */
129
+ findByTag(tag: string): readonly EpisodicMemory[] {
130
+ const results: EpisodicMemory[] = [];
131
+ for (const memory of this.memories.values()) {
132
+ if (memory.tags.includes(tag)) {
133
+ results.push(memory);
134
+ }
135
+ }
136
+ return results;
137
+ }
138
+
139
+ /** Get memory by ID */
140
+ get(id: string): EpisodicMemory | undefined {
141
+ return this.memories.get(id);
142
+ }
143
+
144
+ /** Evict lowest-confidence entries when at capacity */
145
+ private enforceCapacity(): void {
146
+ if (this.memories.size <= this.maxEntries) return;
147
+ const sorted = [...this.memories.values()].sort((a, b) => a.confidence - b.confidence);
148
+ const toRemove = sorted.slice(0, this.memories.size - this.maxEntries);
149
+ for (const mem of toRemove) {
150
+ this.memories.delete(mem.id);
151
+ }
152
+ }
153
+
154
+ get count(): number {
155
+ return this.memories.size;
156
+ }
157
+
158
+ getTotalSplatCount(): number {
159
+ let total = 0;
160
+ for (const mem of this.memories.values()) {
161
+ total += mem.scene.count;
162
+ }
163
+ return total;
164
+ }
165
+ }
166
+
167
+ // ============================================================
168
+ // LONG-TERM PERSISTENT MEMORY (Merkle-signed)
169
+ // ============================================================
170
+
171
+ export class LongTermMemory {
172
+ private rooms: Map<string, LongTermMemory_Entry> = new Map();
173
+ private readonly hmacSecret: string;
174
+
175
+ constructor(hmacSecret: string) {
176
+ if (!hmacSecret || hmacSecret.length < 32) {
177
+ throw new Error('HMAC secret must be at least 32 characters');
178
+ }
179
+ this.hmacSecret = hmacSecret;
180
+ }
181
+
182
+ /** Persist an episodic memory as a Merkle-signed long-term entry */
183
+ persist(roomId: string, scene: SplatScene): LTMEntry {
184
+ // Build Merkle tree from splat data
185
+ const leaves = scene.splats.map(splat => {
186
+ const data = this.serializeSplat(splat);
187
+ return sha256(data);
188
+ });
189
+
190
+ // Handle empty scenes
191
+ if (leaves.length === 0) {
192
+ throw new Error('Cannot persist empty scene');
193
+ }
194
+
195
+ const tree = new MerkleTree(leaves, sha256, { sortPairs: true });
196
+ const merkleRoot = tree.getHexRoot();
197
+ const merkleProof = tree.getHexProof(leaves[0]!);
198
+
199
+ // Sign the entire memory entry
200
+ const signaturePayload = Buffer.from(
201
+ `${roomId}:${merkleRoot}:${scene.count}:${Date.now()}`,
202
+ );
203
+ const signature = hmacSign(signaturePayload, this.hmacSecret);
204
+
205
+ const entry: LTMEntry = {
206
+ id: generateNonce(16),
207
+ roomId,
208
+ scene,
209
+ merkleRoot,
210
+ merkleProof,
211
+ signature,
212
+ createdAt: Date.now(),
213
+ lastVerified: Date.now(),
214
+ splatCount: scene.count,
215
+ };
216
+
217
+ this.rooms.set(`${roomId}:${entry.id}`, entry);
218
+ return entry;
219
+ }
220
+
221
+ /** Verify integrity of a long-term memory entry */
222
+ verify(entry: LTMEntry): boolean {
223
+ // Rebuild Merkle tree and check root
224
+ const leaves = entry.scene.splats.map(splat => {
225
+ const data = this.serializeSplat(splat);
226
+ return sha256(data);
227
+ });
228
+
229
+ if (leaves.length === 0) return false;
230
+
231
+ const tree = new MerkleTree(leaves, sha256, { sortPairs: true });
232
+ const computedRoot = tree.getHexRoot();
233
+ return computedRoot === entry.merkleRoot;
234
+ }
235
+
236
+ /** Get all memories for a room */
237
+ getRoom(roomId: string): readonly LTMEntry[] {
238
+ const results: LTMEntry[] = [];
239
+ for (const [key, value] of this.rooms.entries()) {
240
+ if (key.startsWith(`${roomId}:`)) {
241
+ results.push(value as unknown as LTMEntry);
242
+ }
243
+ }
244
+ return results;
245
+ }
246
+
247
+ /** Serialize a splat to deterministic bytes for hashing */
248
+ private serializeSplat(splat: GaussianSplat): string {
249
+ return JSON.stringify({
250
+ p: [splat.position.x, splat.position.y, splat.position.z],
251
+ s: [splat.scale.x, splat.scale.y, splat.scale.z],
252
+ r: [splat.rotation.w, splat.rotation.x, splat.rotation.y, splat.rotation.z],
253
+ o: splat.opacity,
254
+ sh: Array.from(splat.shCoeffs),
255
+ });
256
+ }
257
+
258
+ get roomCount(): number {
259
+ const rooms = new Set<string>();
260
+ for (const key of this.rooms.keys()) {
261
+ rooms.add(key.split(':')[0]!);
262
+ }
263
+ return rooms.size;
264
+ }
265
+
266
+ get totalEntries(): number {
267
+ return this.rooms.size;
268
+ }
269
+ }
270
+
271
+ // Internal type alias to avoid name collision
272
+ type LongTermMemory_Entry = LTMEntry;
273
+
274
+ // ============================================================
275
+ // MEMORY CONSOLIDATION ENGINE
276
+ // ============================================================
277
+
278
+ export class MemoryConsolidator {
279
+ constructor(
280
+ private readonly shortTerm: ShortTermMemory,
281
+ private readonly midTerm: MidTermMemory,
282
+ private readonly longTerm: LongTermMemory_LT,
283
+ private readonly consolidationThreshold: number = 10, // entries before consolidation
284
+ ) {}
285
+
286
+ /**
287
+ * Consolidate short-term → mid-term
288
+ * Groups nearby frames into episodic memories
289
+ */
290
+ consolidateToMidTerm(tags: string[] = []): ConsolidationEvent | null {
291
+ const entries = this.shortTerm.getAll();
292
+ if (entries.length < this.consolidationThreshold) return null;
293
+
294
+ // Merge all splats from recent entries into a scene
295
+ const allSplats: GaussianSplat[] = [];
296
+ for (const entry of entries) {
297
+ allSplats.push(...entry.splats);
298
+ }
299
+
300
+ if (allSplats.length === 0) return null;
301
+
302
+ // Compute centroid location
303
+ let cx = 0, cy = 0, cz = 0;
304
+ for (const splat of allSplats) {
305
+ cx += splat.position.x;
306
+ cy += splat.position.y;
307
+ cz += splat.position.z;
308
+ }
309
+ const n = allSplats.length;
310
+ const location: Vec3 = { x: cx / n, y: cy / n, z: cz / n };
311
+
312
+ // Downsample to 100K max (mid-term budget)
313
+ const downsampled = this.downsample(allSplats, 100_000);
314
+
315
+ const scene: SplatScene = {
316
+ id: generateNonce(16),
317
+ splats: downsampled,
318
+ count: downsampled.length,
319
+ boundingBox: this.computeAABB(downsampled),
320
+ createdAt: Date.now(),
321
+ };
322
+
323
+ this.midTerm.store(scene, location, tags);
324
+
325
+ const event: ConsolidationEvent = {
326
+ from: 'short' as MemoryTier,
327
+ to: 'mid' as MemoryTier,
328
+ entryCount: entries.length,
329
+ compressionRatio: allSplats.length / downsampled.length,
330
+ timestamp: Date.now(),
331
+ };
332
+
333
+ // Clear consolidated short-term entries
334
+ this.shortTerm.clear();
335
+
336
+ return event;
337
+ }
338
+
339
+ /** Downsample splats using voxel grid filtering */
340
+ private downsample(splats: GaussianSplat[], maxCount: number): GaussianSplat[] {
341
+ if (splats.length <= maxCount) return splats;
342
+
343
+ // Simple stride-based downsampling (production would use voxel grid)
344
+ const stride = Math.ceil(splats.length / maxCount);
345
+ const result: GaussianSplat[] = [];
346
+ for (let i = 0; i < splats.length && result.length < maxCount; i += stride) {
347
+ result.push(splats[i]!);
348
+ }
349
+ return result;
350
+ }
351
+
352
+ /** Compute axis-aligned bounding box */
353
+ private computeAABB(splats: GaussianSplat[]): AABB {
354
+ if (splats.length === 0) {
355
+ return { min: { x: 0, y: 0, z: 0 }, max: { x: 0, y: 0, z: 0 } };
356
+ }
357
+ let minX = Infinity, minY = Infinity, minZ = Infinity;
358
+ let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
359
+ for (const s of splats) {
360
+ if (s.position.x < minX) minX = s.position.x;
361
+ if (s.position.y < minY) minY = s.position.y;
362
+ if (s.position.z < minZ) minZ = s.position.z;
363
+ if (s.position.x > maxX) maxX = s.position.x;
364
+ if (s.position.y > maxY) maxY = s.position.y;
365
+ if (s.position.z > maxZ) maxZ = s.position.z;
366
+ }
367
+ return {
368
+ min: { x: minX, y: minY, z: minZ },
369
+ max: { x: maxX, y: maxY, z: maxZ },
370
+ };
371
+ }
372
+ }
373
+
374
+ // Alias for long-term memory class used in consolidator
375
+ type LongTermMemory_LT = InstanceType<typeof LongTermMemory>;
@@ -0,0 +1 @@
1
+ export { OccupancyGrid, aStarPath, rrtStarPath, planPath } from './pathfinding.js';