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.
- package/.cursorrules +74 -0
- package/CLAUDE.md +61 -0
- package/LICENSE +190 -0
- package/README.md +107 -0
- package/dist/index.js +194 -0
- package/package.json +84 -0
- package/src/antispoofing/detector.ts +509 -0
- package/src/antispoofing/index.ts +7 -0
- package/src/gamification/badges.ts +429 -0
- package/src/gamification/fleet-leaderboard.ts +293 -0
- package/src/gamification/index.ts +44 -0
- package/src/gamification/streaks.ts +243 -0
- package/src/gamification/trust-tiers.ts +393 -0
- package/src/gamification/zone-mastery.ts +256 -0
- package/src/index.ts +341 -0
- package/src/memory/index.ts +9 -0
- package/src/memory/place-cells.ts +279 -0
- package/src/memory/spatial-memory.ts +375 -0
- package/src/navigation/index.ts +1 -0
- package/src/navigation/pathfinding.ts +403 -0
- package/src/perception/camera.ts +249 -0
- package/src/perception/index.ts +2 -0
- package/src/types/index.ts +416 -0
- package/src/utils/crypto.ts +94 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/math.ts +204 -0
- package/src/verification/index.ts +9 -0
- package/src/verification/spatial-proof.ts +442 -0
- package/tests/antispoofing/detector.test.ts +196 -0
- package/tests/gamification/badges.test.ts +163 -0
- package/tests/gamification/fleet-leaderboard.test.ts +181 -0
- package/tests/gamification/streaks.test.ts +158 -0
- package/tests/gamification/trust-tiers.test.ts +165 -0
- package/tests/gamification/zone-mastery.test.ts +143 -0
- package/tests/memory/place-cells.test.ts +128 -0
- package/tests/stress/load.test.ts +499 -0
- package/tests/stress/security.test.ts +378 -0
- package/tests/stress/simulation.test.ts +361 -0
- package/tests/utils/crypto.test.ts +115 -0
- package/tests/utils/math.test.ts +195 -0
- package/tests/verification/spatial-proof.test.ts +299 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
TrustTier,
|
|
4
|
+
TrustTierSystem,
|
|
5
|
+
verifyTierChange,
|
|
6
|
+
TIER_CONFIGS,
|
|
7
|
+
} from '../../src/gamification/trust-tiers.js';
|
|
8
|
+
|
|
9
|
+
const SECRET = 'a'.repeat(32);
|
|
10
|
+
|
|
11
|
+
describe('TrustTierSystem', () => {
|
|
12
|
+
it('requires 32-char secret', () => {
|
|
13
|
+
expect(() => new TrustTierSystem('short')).toThrow('32 chars');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('registers a robot at Untrusted', () => {
|
|
17
|
+
const system = new TrustTierSystem(SECRET);
|
|
18
|
+
const profile = system.register('robot-1');
|
|
19
|
+
expect(profile.currentTier).toBe(TrustTier.UNTRUSTED);
|
|
20
|
+
expect(profile.points).toBe(0);
|
|
21
|
+
expect(profile.history).toHaveLength(0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('rejects duplicate registration', () => {
|
|
25
|
+
const system = new TrustTierSystem(SECRET);
|
|
26
|
+
system.register('robot-1');
|
|
27
|
+
expect(() => system.register('robot-1')).toThrow('already registered');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('rejects empty robotId', () => {
|
|
31
|
+
const system = new TrustTierSystem(SECRET);
|
|
32
|
+
expect(() => system.register('')).toThrow('robotId is required');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('awards points on success', () => {
|
|
36
|
+
const system = new TrustTierSystem(SECRET);
|
|
37
|
+
system.register('robot-1');
|
|
38
|
+
const profile = system.recordSuccess('robot-1', 50);
|
|
39
|
+
expect(profile.points).toBe(50);
|
|
40
|
+
expect(profile.successfulVerifications).toBe(1);
|
|
41
|
+
expect(profile.consecutiveSuccesses).toBe(1);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('promotes to Probation at 100 points', () => {
|
|
45
|
+
const system = new TrustTierSystem(SECRET);
|
|
46
|
+
system.register('robot-1');
|
|
47
|
+
// Record 10 successes of 10 points each = 100 points
|
|
48
|
+
for (let i = 0; i < 10; i++) {
|
|
49
|
+
system.recordSuccess('robot-1', 10);
|
|
50
|
+
}
|
|
51
|
+
const profile = system.getProfile('robot-1')!;
|
|
52
|
+
expect(profile.currentTier).toBe(TrustTier.PROBATION);
|
|
53
|
+
expect(profile.history.length).toBeGreaterThan(0);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('tier change events are HMAC-signed', () => {
|
|
57
|
+
const system = new TrustTierSystem(SECRET);
|
|
58
|
+
system.register('robot-1');
|
|
59
|
+
for (let i = 0; i < 10; i++) system.recordSuccess('robot-1', 10);
|
|
60
|
+
const profile = system.getProfile('robot-1')!;
|
|
61
|
+
const event = profile.history[0]!;
|
|
62
|
+
expect(verifyTierChange(event, SECRET)).toBe(true);
|
|
63
|
+
expect(verifyTierChange(event, 'b'.repeat(32))).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('penalizes on failure (10% point loss)', () => {
|
|
67
|
+
const system = new TrustTierSystem(SECRET);
|
|
68
|
+
system.register('robot-1');
|
|
69
|
+
system.recordSuccess('robot-1', 200);
|
|
70
|
+
const profile = system.recordFailure('robot-1', false);
|
|
71
|
+
expect(profile.points).toBe(180); // 200 - 10% = 180
|
|
72
|
+
expect(profile.consecutiveSuccesses).toBe(0);
|
|
73
|
+
expect(profile.failedVerifications).toBe(1);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('penalizes 25% on spoofing', () => {
|
|
77
|
+
const system = new TrustTierSystem(SECRET);
|
|
78
|
+
system.register('robot-1');
|
|
79
|
+
system.recordSuccess('robot-1', 200);
|
|
80
|
+
const profile = system.recordFailure('robot-1', true);
|
|
81
|
+
expect(profile.points).toBe(150); // 200 - 25% = 150
|
|
82
|
+
expect(profile.spoofingIncidents).toBe(1);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('demotes when points drop below tier minimum', () => {
|
|
86
|
+
const system = new TrustTierSystem(SECRET);
|
|
87
|
+
system.register('robot-1');
|
|
88
|
+
// Promote to Probation (100 pts)
|
|
89
|
+
for (let i = 0; i < 10; i++) system.recordSuccess('robot-1', 10);
|
|
90
|
+
expect(system.getProfile('robot-1')!.currentTier).toBe(TrustTier.PROBATION);
|
|
91
|
+
// Spoofing attack: lose 25% of 100 = 25, new balance 75 < 100 min
|
|
92
|
+
const profile = system.recordFailure('robot-1', true);
|
|
93
|
+
expect(profile.currentTier).toBe(TrustTier.UNTRUSTED);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('drops two tiers for spoofing at Trusted+', () => {
|
|
97
|
+
const system = new TrustTierSystem(SECRET);
|
|
98
|
+
system.register('robot-1');
|
|
99
|
+
// Fast-track to Trusted (need 2000 pts, >90% success rate, 0 spoofing)
|
|
100
|
+
for (let i = 0; i < 200; i++) system.recordSuccess('robot-1', 10);
|
|
101
|
+
const before = system.getProfile('robot-1')!;
|
|
102
|
+
expect(before.currentTier).toBeGreaterThanOrEqual(TrustTier.TRUSTED);
|
|
103
|
+
|
|
104
|
+
// Spoofing should drop 2 tiers
|
|
105
|
+
const afterTier = before.currentTier;
|
|
106
|
+
const profile = system.recordFailure('robot-1', true);
|
|
107
|
+
expect(profile.currentTier).toBeLessThanOrEqual(afterTier - 2);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('fee multiplier decreases with tier', () => {
|
|
111
|
+
expect(TIER_CONFIGS[TrustTier.UNTRUSTED].feeMultiplier).toBe(2.5);
|
|
112
|
+
expect(TIER_CONFIGS[TrustTier.AUTONOMOUS].feeMultiplier).toBe(0.8);
|
|
113
|
+
// Ensure monotonically decreasing
|
|
114
|
+
for (let i = 1; i <= TrustTier.AUTONOMOUS; i++) {
|
|
115
|
+
expect(TIER_CONFIGS[i as TrustTier].feeMultiplier)
|
|
116
|
+
.toBeLessThanOrEqual(TIER_CONFIGS[(i - 1) as TrustTier].feeMultiplier);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('getFeeMultiplier returns correct value', () => {
|
|
121
|
+
const system = new TrustTierSystem(SECRET);
|
|
122
|
+
system.register('robot-1');
|
|
123
|
+
expect(system.getFeeMultiplier('robot-1')).toBe(2.5);
|
|
124
|
+
expect(system.getFeeMultiplier('unknown')).toBe(2.5); // defaults to untrusted
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('isWithinLimits checks tier limits', () => {
|
|
128
|
+
const system = new TrustTierSystem(SECRET);
|
|
129
|
+
system.register('robot-1');
|
|
130
|
+
expect(system.isWithinLimits('robot-1', 5)).toBe(true);
|
|
131
|
+
expect(system.isWithinLimits('robot-1', 15)).toBe(false); // max $10 for Untrusted
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('requiresVerification respects frequency', () => {
|
|
135
|
+
const system = new TrustTierSystem(SECRET);
|
|
136
|
+
system.register('robot-1');
|
|
137
|
+
// Untrusted: verify every operation (freq=1)
|
|
138
|
+
expect(system.requiresVerification('robot-1', 0)).toBe(true);
|
|
139
|
+
expect(system.requiresVerification('robot-1', 1)).toBe(true);
|
|
140
|
+
expect(system.requiresVerification('robot-1', 5)).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('verifyHistory validates entire chain', () => {
|
|
144
|
+
const system = new TrustTierSystem(SECRET);
|
|
145
|
+
system.register('robot-1');
|
|
146
|
+
for (let i = 0; i < 10; i++) system.recordSuccess('robot-1', 10);
|
|
147
|
+
const result = system.verifyHistory('robot-1');
|
|
148
|
+
expect(result.valid).toBe(true);
|
|
149
|
+
expect(result.invalidEvents).toHaveLength(0);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('tracks robot count', () => {
|
|
153
|
+
const system = new TrustTierSystem(SECRET);
|
|
154
|
+
expect(system.robotCount).toBe(0);
|
|
155
|
+
system.register('r1');
|
|
156
|
+
system.register('r2');
|
|
157
|
+
expect(system.robotCount).toBe(2);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('rejects negative points', () => {
|
|
161
|
+
const system = new TrustTierSystem(SECRET);
|
|
162
|
+
system.register('robot-1');
|
|
163
|
+
expect(() => system.recordSuccess('robot-1', -10)).toThrow('non-negative');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ZoneMasterySystem } from '../../src/gamification/zone-mastery.js';
|
|
3
|
+
|
|
4
|
+
const SECRET = 'a'.repeat(32);
|
|
5
|
+
|
|
6
|
+
describe('ZoneMasterySystem', () => {
|
|
7
|
+
it('requires 32-char secret', () => {
|
|
8
|
+
expect(() => new ZoneMasterySystem('short')).toThrow('32 chars');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('defines a zone', () => {
|
|
12
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
13
|
+
const zone = system.defineZone('Warehouse A', {
|
|
14
|
+
min: { x: 0, y: 0, z: 0 },
|
|
15
|
+
max: { x: 50, y: 50, z: 5 },
|
|
16
|
+
});
|
|
17
|
+
expect(zone.id).toBeTruthy();
|
|
18
|
+
expect(zone.name).toBe('Warehouse A');
|
|
19
|
+
expect(system.totalZones).toBe(1);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('rejects invalid bounds', () => {
|
|
23
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
24
|
+
expect(() => system.defineZone('Bad', {
|
|
25
|
+
min: { x: 50, y: 0, z: 0 },
|
|
26
|
+
max: { x: 10, y: 50, z: 5 },
|
|
27
|
+
})).toThrow('min must be less than max');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('finds zone for position', () => {
|
|
31
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
32
|
+
system.defineZone('Zone A', {
|
|
33
|
+
min: { x: 0, y: 0, z: 0 },
|
|
34
|
+
max: { x: 50, y: 50, z: 5 },
|
|
35
|
+
});
|
|
36
|
+
const found = system.findZone({ x: 25, y: 25, z: 2 });
|
|
37
|
+
expect(found).toBeDefined();
|
|
38
|
+
expect(found!.name).toBe('Zone A');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('returns undefined for position outside all zones', () => {
|
|
42
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
43
|
+
system.defineZone('Zone A', {
|
|
44
|
+
min: { x: 0, y: 0, z: 0 },
|
|
45
|
+
max: { x: 50, y: 50, z: 5 },
|
|
46
|
+
});
|
|
47
|
+
expect(system.findZone({ x: 100, y: 100, z: 0 })).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('records visit and returns mastery', () => {
|
|
51
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
52
|
+
system.defineZone('Zone A', {
|
|
53
|
+
min: { x: 0, y: 0, z: 0 },
|
|
54
|
+
max: { x: 50, y: 50, z: 5 },
|
|
55
|
+
});
|
|
56
|
+
const result = system.recordVisit('robot-1', { x: 10, y: 10, z: 1 }, true, 50);
|
|
57
|
+
expect(result.zoneId).toBeTruthy();
|
|
58
|
+
expect(result.mastery).toBeDefined();
|
|
59
|
+
expect(result.mastery!.successRate).toBe(1.0);
|
|
60
|
+
expect(result.mastery!.composite).toBeGreaterThan(0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('returns null for position outside zones', () => {
|
|
64
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
65
|
+
const result = system.recordVisit('robot-1', { x: 999, y: 999, z: 0 }, true);
|
|
66
|
+
expect(result.zoneId).toBeNull();
|
|
67
|
+
expect(result.mastery).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('coverage increases with unique positions', () => {
|
|
71
|
+
const system = new ZoneMasterySystem(SECRET, 2.0);
|
|
72
|
+
system.defineZone('Zone A', {
|
|
73
|
+
min: { x: 0, y: 0, z: 0 },
|
|
74
|
+
max: { x: 10, y: 10, z: 2 },
|
|
75
|
+
});
|
|
76
|
+
const r1 = system.recordVisit('robot-1', { x: 1, y: 1, z: 1 }, true);
|
|
77
|
+
const r2 = system.recordVisit('robot-1', { x: 5, y: 5, z: 1 }, true);
|
|
78
|
+
const r3 = system.recordVisit('robot-1', { x: 9, y: 9, z: 1 }, true);
|
|
79
|
+
expect(r3.mastery!.coverage).toBeGreaterThan(r1.mastery!.coverage);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('success rate reflects failures', () => {
|
|
83
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
84
|
+
system.defineZone('Zone A', {
|
|
85
|
+
min: { x: 0, y: 0, z: 0 },
|
|
86
|
+
max: { x: 50, y: 50, z: 5 },
|
|
87
|
+
});
|
|
88
|
+
system.recordVisit('robot-1', { x: 10, y: 10, z: 1 }, true);
|
|
89
|
+
system.recordVisit('robot-1', { x: 15, y: 15, z: 1 }, false);
|
|
90
|
+
const mastery = system.getMastery('robot-1', system.findZone({ x: 10, y: 10, z: 1 })!.id);
|
|
91
|
+
expect(mastery!.successRate).toBe(0.5);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('getZoneCount counts visited zones', () => {
|
|
95
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
96
|
+
system.defineZone('A', { min: { x: 0, y: 0, z: 0 }, max: { x: 10, y: 10, z: 5 } });
|
|
97
|
+
system.defineZone('B', { min: { x: 20, y: 20, z: 0 }, max: { x: 30, y: 30, z: 5 } });
|
|
98
|
+
system.recordVisit('robot-1', { x: 5, y: 5, z: 1 }, true);
|
|
99
|
+
system.recordVisit('robot-1', { x: 25, y: 25, z: 1 }, true);
|
|
100
|
+
expect(system.getZoneCount('robot-1')).toBe(2);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('getMaxMastery returns highest zone score', () => {
|
|
104
|
+
const system = new ZoneMasterySystem(SECRET, 2.0);
|
|
105
|
+
system.defineZone('A', { min: { x: 0, y: 0, z: 0 }, max: { x: 10, y: 10, z: 2 } });
|
|
106
|
+
system.defineZone('B', { min: { x: 20, y: 20, z: 0 }, max: { x: 30, y: 30, z: 2 } });
|
|
107
|
+
// Visit A multiple times
|
|
108
|
+
for (let i = 0; i < 10; i++) {
|
|
109
|
+
system.recordVisit('robot-1', { x: i, y: i, z: 1 }, true);
|
|
110
|
+
}
|
|
111
|
+
// Visit B once
|
|
112
|
+
system.recordVisit('robot-1', { x: 25, y: 25, z: 1 }, true);
|
|
113
|
+
const max = system.getMaxMastery('robot-1');
|
|
114
|
+
expect(max).toBeGreaterThan(0);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('getAllMastery returns scores for all zones', () => {
|
|
118
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
119
|
+
system.defineZone('A', { min: { x: 0, y: 0, z: 0 }, max: { x: 10, y: 10, z: 5 } });
|
|
120
|
+
system.defineZone('B', { min: { x: 20, y: 20, z: 0 }, max: { x: 30, y: 30, z: 5 } });
|
|
121
|
+
system.recordVisit('robot-1', { x: 5, y: 5, z: 1 }, true);
|
|
122
|
+
system.recordVisit('robot-1', { x: 25, y: 25, z: 1 }, true);
|
|
123
|
+
const scores = system.getAllMastery('robot-1');
|
|
124
|
+
expect(scores).toHaveLength(2);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('signMasterySnapshot produces HMAC-signed output', () => {
|
|
128
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
129
|
+
system.defineZone('A', { min: { x: 0, y: 0, z: 0 }, max: { x: 10, y: 10, z: 5 } });
|
|
130
|
+
system.recordVisit('robot-1', { x: 5, y: 5, z: 1 }, true);
|
|
131
|
+
const snapshot = system.signMasterySnapshot('robot-1');
|
|
132
|
+
expect(snapshot.scores).toHaveLength(1);
|
|
133
|
+
expect(snapshot.signature).toHaveLength(64);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('findAllZones returns overlapping zones', () => {
|
|
137
|
+
const system = new ZoneMasterySystem(SECRET);
|
|
138
|
+
system.defineZone('A', { min: { x: 0, y: 0, z: 0 }, max: { x: 20, y: 20, z: 5 } });
|
|
139
|
+
system.defineZone('B', { min: { x: 10, y: 10, z: 0 }, max: { x: 30, y: 30, z: 5 } });
|
|
140
|
+
const zones = system.findAllZones({ x: 15, y: 15, z: 2 });
|
|
141
|
+
expect(zones).toHaveLength(2);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createPlaceCell,
|
|
4
|
+
PlaceCellPopulation,
|
|
5
|
+
createGridCell,
|
|
6
|
+
GridCellModule,
|
|
7
|
+
GridCellSystem,
|
|
8
|
+
computeSpatialCode,
|
|
9
|
+
} from '../../src/memory/place-cells.js';
|
|
10
|
+
|
|
11
|
+
describe('Place Cells', () => {
|
|
12
|
+
it('creates a place cell with correct properties', () => {
|
|
13
|
+
const cell = createPlaceCell({ x: 5, y: 10, z: 0 }, 2.0, 1.0);
|
|
14
|
+
expect(cell.center).toEqual({ x: 5, y: 10, z: 0 });
|
|
15
|
+
expect(cell.radius).toBe(2.0);
|
|
16
|
+
expect(cell.peakRate).toBe(1.0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('activation peaks at center', () => {
|
|
20
|
+
const cell = createPlaceCell({ x: 0, y: 0, z: 0 }, 2.0);
|
|
21
|
+
expect(cell.activation({ x: 0, y: 0, z: 0 })).toBeCloseTo(1.0);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('activation decreases with distance', () => {
|
|
25
|
+
const cell = createPlaceCell({ x: 0, y: 0, z: 0 }, 2.0);
|
|
26
|
+
const atCenter = cell.activation({ x: 0, y: 0, z: 0 });
|
|
27
|
+
const at1m = cell.activation({ x: 1, y: 0, z: 0 });
|
|
28
|
+
const at3m = cell.activation({ x: 3, y: 0, z: 0 });
|
|
29
|
+
expect(atCenter).toBeGreaterThan(at1m);
|
|
30
|
+
expect(at1m).toBeGreaterThan(at3m);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('activation is near zero far from center', () => {
|
|
34
|
+
const cell = createPlaceCell({ x: 0, y: 0, z: 0 }, 2.0);
|
|
35
|
+
expect(cell.activation({ x: 20, y: 20, z: 20 })).toBeLessThan(0.001);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe('PlaceCellPopulation', () => {
|
|
40
|
+
it('adds cells and rejects too-close duplicates', () => {
|
|
41
|
+
const pop = new PlaceCellPopulation(1.5);
|
|
42
|
+
const c1 = createPlaceCell({ x: 0, y: 0, z: 0 });
|
|
43
|
+
const c2 = createPlaceCell({ x: 1, y: 0, z: 0 }); // too close (1m < 1.5m)
|
|
44
|
+
const c3 = createPlaceCell({ x: 5, y: 5, z: 0 }); // far enough
|
|
45
|
+
expect(pop.add(c1)).toBe(true);
|
|
46
|
+
expect(pop.add(c2)).toBe(false);
|
|
47
|
+
expect(pop.add(c3)).toBe(true);
|
|
48
|
+
expect(pop.count).toBe(2);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('covers a region with place cells', () => {
|
|
52
|
+
const pop = new PlaceCellPopulation(1.5);
|
|
53
|
+
const count = pop.coverRegion(
|
|
54
|
+
{ x: 0, y: 0, z: 0 },
|
|
55
|
+
{ x: 10, y: 10, z: 0 },
|
|
56
|
+
2.0,
|
|
57
|
+
);
|
|
58
|
+
expect(count).toBeGreaterThan(0);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('gets activations for a position', () => {
|
|
62
|
+
const pop = new PlaceCellPopulation(1.5);
|
|
63
|
+
pop.coverRegion({ x: 0, y: 0, z: 0 }, { x: 10, y: 10, z: 0 }, 2.0);
|
|
64
|
+
const activations = pop.getActivations({ x: 5, y: 5, z: 0 });
|
|
65
|
+
expect(activations.size).toBeGreaterThan(0);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('decodes position from activations', () => {
|
|
69
|
+
const pop = new PlaceCellPopulation(1.5);
|
|
70
|
+
pop.coverRegion({ x: 0, y: 0, z: 0 }, { x: 10, y: 10, z: 0 }, 2.0);
|
|
71
|
+
const target = { x: 5, y: 5, z: 0 };
|
|
72
|
+
const activations = pop.getActivations(target);
|
|
73
|
+
const decoded = pop.decodePosition(activations);
|
|
74
|
+
// Decoded position should be close to actual position
|
|
75
|
+
expect(Math.abs(decoded.x - target.x)).toBeLessThan(2);
|
|
76
|
+
expect(Math.abs(decoded.y - target.y)).toBeLessThan(2);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('Grid Cells', () => {
|
|
81
|
+
it('creates grid cell with periodic activation', () => {
|
|
82
|
+
const cell = createGridCell(2.0, 0);
|
|
83
|
+
const a1 = cell.activation({ x: 0, y: 0, z: 0 });
|
|
84
|
+
const a2 = cell.activation({ x: 2, y: 0, z: 0 }); // one period away
|
|
85
|
+
// Should have similar activation (periodic)
|
|
86
|
+
// Grid cell activation is periodic but phase-dependent; just verify both are in valid range
|
|
87
|
+
expect(a1).toBeGreaterThanOrEqual(0);
|
|
88
|
+
expect(a1).toBeLessThanOrEqual(1);
|
|
89
|
+
expect(a2).toBeGreaterThanOrEqual(0);
|
|
90
|
+
expect(a2).toBeLessThanOrEqual(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('activation is bounded [0,1]', () => {
|
|
94
|
+
const cell = createGridCell(1.0, 0);
|
|
95
|
+
for (let x = 0; x < 10; x += 0.1) {
|
|
96
|
+
const a = cell.activation({ x, y: 0, z: 0 });
|
|
97
|
+
expect(a).toBeGreaterThanOrEqual(0);
|
|
98
|
+
expect(a).toBeLessThanOrEqual(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('GridCellSystem', () => {
|
|
104
|
+
it('creates multi-scale modules', () => {
|
|
105
|
+
const system = new GridCellSystem(0.5, Math.SQRT2, 4, 16);
|
|
106
|
+
expect(system.totalCells).toBe(64); // 4 modules * 16 cells
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('returns activations for all cells', () => {
|
|
110
|
+
const system = new GridCellSystem(0.5, Math.SQRT2, 4, 8);
|
|
111
|
+
const activations = system.getActivations({ x: 3, y: 4, z: 0 });
|
|
112
|
+
expect(activations.size).toBe(32); // 4 * 8
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('Spatial Code', () => {
|
|
117
|
+
it('computes combined spatial code', () => {
|
|
118
|
+
const placeCells = new PlaceCellPopulation(1.5);
|
|
119
|
+
placeCells.coverRegion({ x: 0, y: 0, z: 0 }, { x: 10, y: 10, z: 0 }, 2.0);
|
|
120
|
+
const gridCells = new GridCellSystem(0.5, Math.SQRT2, 2, 8);
|
|
121
|
+
|
|
122
|
+
const code = computeSpatialCode({ x: 5, y: 5, z: 0 }, placeCells, gridCells);
|
|
123
|
+
expect(code.placeCellActivations.size).toBeGreaterThan(0);
|
|
124
|
+
expect(code.gridCellActivations.size).toBeGreaterThan(0);
|
|
125
|
+
expect(code.confidence).toBeGreaterThan(0);
|
|
126
|
+
expect(code.timestamp).toBeGreaterThan(0);
|
|
127
|
+
});
|
|
128
|
+
});
|