@holoscript/framework 6.0.3 → 6.0.4
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/CHANGELOG.md +1 -2
- package/ROADMAP.md +68 -66
- package/dist/{InvisibleWallet-BB6tFvRA.d.cts → InvisibleWallet-EFiuaLn3.d.cts} +1 -1
- package/dist/{OrchestratorAgent-BvWgf9uw.d.cts → OrchestratorAgent-CrLDGNL6.d.cts} +1 -1
- package/dist/agents/index.cjs +11 -10
- package/dist/agents/index.d.cts +4 -16
- package/dist/ai/index.cjs +2 -2
- package/dist/behavior.cjs +10 -0
- package/dist/economy/index.cjs +4 -4
- package/dist/economy/index.d.cts +2 -2
- package/dist/index.cjs +33 -11
- package/dist/index.d.cts +3 -3
- package/dist/swarm/index.cjs +3 -0
- package/package.json +14 -9
- package/src/__tests__/bounty-marketplace.test.ts +53 -21
- package/src/__tests__/delegation.test.ts +1 -4
- package/src/__tests__/done-log-audit.test.ts +38 -46
- package/src/__tests__/framework.test.ts +172 -53
- package/src/__tests__/goal-synthesizer.test.ts +9 -6
- package/src/__tests__/presence.test.ts +1 -1
- package/src/__tests__/protocol-agent.test.ts +12 -11
- package/src/__tests__/revenue-splitter.test.ts +22 -15
- package/src/__tests__/scenario-driven-todo.test.ts +55 -35
- package/src/__tests__/self-improve.test.ts +28 -9
- package/src/__tests__/service-lifecycle.test.ts +9 -3
- package/src/__tests__/skill-router.test.ts +3 -3
- package/src/agents/CulturalMemory.ts +6 -6
- package/src/agents/DelegationTraceHooks.ts +560 -0
- package/src/agents/FederatedRegistryAdapter.ts +1 -1
- package/src/agents/NormEngine.ts +3 -8
- package/src/agents/OrchestratorAgent.ts +1 -1
- package/src/agents/TaskDelegationService.ts +5 -9
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +5 -4
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +9 -3
- package/src/agents/__tests__/DelegationTraceHooks.test.ts +390 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +4 -2
- package/src/agents/spatial-comms/Layer1RealTime.ts +36 -19
- package/src/agents/spatial-comms/Layer2A2A.ts +1 -3
- package/src/agents/spatial-comms/Layer3MCP.ts +13 -4
- package/src/agents/spatial-comms/ProtocolTypes.ts +5 -2
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +2 -2
- package/src/ai/HoloScriptGenerator.ts +2 -2
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +1 -1
- package/src/ai/__tests__/PerceptionSystem.test.ts +14 -14
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +1 -1
- package/src/ai/index.ts +5 -1
- package/src/board/audit.ts +17 -6
- package/src/board/board-ops.ts +45 -15
- package/src/board/board-types.ts +94 -20
- package/src/delegation.ts +5 -3
- package/src/distributed-claimer.ts +13 -2
- package/src/economy/BountyManager.ts +40 -18
- package/src/economy/KnowledgeMarketplace.ts +27 -8
- package/src/economy/PaymentWebhookService.ts +0 -1
- package/src/economy/RevenueSplitter.ts +2 -4
- package/src/economy/UnifiedBudgetOptimizer.ts +8 -9
- package/src/economy/_core-stubs.ts +1 -1
- package/src/economy/x402-facilitator.ts +17 -8
- package/src/index.ts +16 -12
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +138 -89
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +59 -16
- package/src/knowledge/brain.ts +7 -7
- package/src/knowledge/consolidation.ts +16 -16
- package/src/knowledge/knowledge-consolidator.ts +60 -30
- package/src/knowledge/knowledge-store.ts +83 -45
- package/src/learning/ProceduralCompiler.ts +6 -1
- package/src/learning/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/learning/MemoryScorer.ts +69 -0
- package/src/learning/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/learning/SemanticClusterer.ts +66 -0
- package/src/llm/llm-adapter.ts +24 -10
- package/src/mesh/index.ts +37 -17
- package/src/protocol/goal-synthesizer.ts +24 -34
- package/src/protocol/implementations.ts +91 -22
- package/src/protocol/micro-phase-decomposer.ts +25 -17
- package/src/protocol/micro-step-decomposer.test.ts +104 -39
- package/src/protocol-agent.test.ts +17 -7
- package/src/protocol-agent.ts +45 -42
- package/src/self-improve/absorb-scanner.ts +9 -6
- package/src/self-improve/evolution-engine.ts +36 -18
- package/src/self-improve/framework-absorber.ts +21 -16
- package/src/self-improve/index.ts +2 -10
- package/src/self-improve/prompt-optimizer.ts +31 -19
- package/src/self-improve/test-generator.ts +16 -12
- package/src/skill-router.ts +7 -6
- package/src/swarm/messaging/GossipProtocol.ts +1 -1
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +31 -9
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +21 -7
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +24 -8
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +6 -2
- package/src/team.ts +277 -122
- package/src/training/scripts/generate-spatial-dataset.ts +1 -1
- package/src/training/training/LRScheduler.ts +377 -0
- package/src/training/training/QualityScoringPipeline.ts +139 -0
- package/src/training/training/SoftDedup.ts +461 -0
- package/src/training/training/SparsityMonitor.ts +685 -0
- package/src/training/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/training/__tests__/CorpusValidation.test.ts +87 -0
- package/src/training/training/__tests__/LRScheduler.test.ts +592 -0
- package/src/training/training/__tests__/SoftDedup.test.ts +415 -0
- package/src/training/training/__tests__/SparsityMonitor.test.ts +1623 -0
- package/src/training/training/__tests__/SpatialCorpusValidation.test.ts +72 -0
- package/src/training/training/__tests__/SpatialTrainingDataGenerator.test.ts +1244 -0
- package/src/training/training/__tests__/TrainingMonkeyIntegration.test.ts +897 -0
- package/src/training/training/__tests__/TrainingPipelineConfig.test.ts +202 -0
- package/src/training/training/__tests__/schema.test.ts +72 -0
- package/src/training/training/__tests__/training-constants.test.ts +106 -0
- package/src/training/training/__tests__/trait-mappings.test.ts +81 -0
- package/src/training/training/constants.ts +94 -0
- package/src/training/training/index.ts +17 -0
- package/src/training/training/schema.ts +147 -0
- package/src/training/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/training/trainingmonkey/index.ts +26 -0
- package/src/training/training/trait-mappings.ts +157 -0
- package/src/types.ts +2 -7
- package/ALL-test-results.json +0 -1
- package/LICENSE +0 -21
- package/dist/AgentManifest-CB4xM-Ma.d.ts +0 -704
- package/dist/BehaviorTree-BrBFECv5.d.ts +0 -103
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +0 -1732
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +0 -798
- package/dist/agents/index.d.ts +0 -1788
- package/dist/agents/index.js +0 -4695
- package/dist/ai/index.d.ts +0 -1753
- package/dist/ai/index.js +0 -5244
- package/dist/behavior.d.ts +0 -130
- package/dist/behavior.js +0 -407
- package/dist/economy/index.d.ts +0 -747
- package/dist/economy/index.js +0 -3617
- package/dist/implementations-D9T3un9D.d.ts +0 -236
- package/dist/index.d.ts +0 -1729
- package/dist/index.js +0 -24277
- package/dist/learning/index.d.ts +0 -104
- package/dist/learning/index.js +0 -189
- package/dist/negotiation/index.d.ts +0 -610
- package/dist/negotiation/index.js +0 -931
- package/dist/skills/index.d.ts +0 -289
- package/dist/skills/index.js +0 -1079
- package/dist/swarm/index.d.ts +0 -2433
- package/dist/swarm/index.js +0 -5221
- package/dist/training/index.d.ts +0 -1734
- package/dist/training/index.js +0 -2687
- package/extract-failures.js +0 -10
- package/src/training/training/data/novel-use-cases.jsonl +0 -153
- package/src/training/training/data/spatial-reasoning-10k.jsonl +0 -9354
- package/src/types/core-stubs.d.ts +0 -113
- package/test-output.txt +0 -0
- package/test-result.json +0 -1
- package/tsc-errors.txt +0 -4
- package/tsc_output.txt +0 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +0 -22
- package/vitest-log-utf8.txt +0 -268
- package/vitest-log.txt +0 -0
|
@@ -0,0 +1,1244 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
SpatialTrainingDataGenerator,
|
|
4
|
+
createSpatialTrainingDataGenerator,
|
|
5
|
+
} from '../SpatialTrainingDataGenerator';
|
|
6
|
+
import type {
|
|
7
|
+
SpatialTrainingExample,
|
|
8
|
+
SpatialGeneratorConfig,
|
|
9
|
+
SpatialDifficulty,
|
|
10
|
+
SpatialRelationshipType,
|
|
11
|
+
SpatialTrainingJSONLEntry,
|
|
12
|
+
} from '../SpatialTrainingDataTypes';
|
|
13
|
+
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// HELPERS
|
|
16
|
+
// =============================================================================
|
|
17
|
+
|
|
18
|
+
const FIXED_SEED = 42;
|
|
19
|
+
|
|
20
|
+
function createSeededGenerator(
|
|
21
|
+
overrides: Partial<SpatialGeneratorConfig> = {}
|
|
22
|
+
): SpatialTrainingDataGenerator {
|
|
23
|
+
return new SpatialTrainingDataGenerator({
|
|
24
|
+
seed: FIXED_SEED,
|
|
25
|
+
...overrides,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// TESTS
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
describe('SpatialTrainingDataGenerator', () => {
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Initialization
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
describe('initialization', () => {
|
|
39
|
+
it('should create a generator with default config', () => {
|
|
40
|
+
const gen = new SpatialTrainingDataGenerator();
|
|
41
|
+
expect(gen).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should create a generator via factory function', () => {
|
|
45
|
+
const gen = createSpatialTrainingDataGenerator();
|
|
46
|
+
expect(gen).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should create a generator with custom config', () => {
|
|
50
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
51
|
+
examplesPerCategory: 5,
|
|
52
|
+
seed: 123,
|
|
53
|
+
positiveRatio: 0.7,
|
|
54
|
+
});
|
|
55
|
+
expect(gen).toBeDefined();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should accept all configuration options', () => {
|
|
59
|
+
const config: SpatialGeneratorConfig = {
|
|
60
|
+
examplesPerCategory: 3,
|
|
61
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
62
|
+
difficultyLevels: ['basic'],
|
|
63
|
+
positiveRatio: 0.6,
|
|
64
|
+
seed: 99,
|
|
65
|
+
includeContext: false,
|
|
66
|
+
};
|
|
67
|
+
const gen = new SpatialTrainingDataGenerator(config);
|
|
68
|
+
expect(gen).toBeDefined();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Generation: basic output structure
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
describe('generate()', () => {
|
|
77
|
+
let gen: SpatialTrainingDataGenerator;
|
|
78
|
+
|
|
79
|
+
beforeEach(() => {
|
|
80
|
+
gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should generate examples', () => {
|
|
84
|
+
const examples = gen.generate();
|
|
85
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should generate expected number of examples', () => {
|
|
89
|
+
// 3 relationship types x 3 difficulty levels x 3 examples per category = 27
|
|
90
|
+
const examples = gen.generate();
|
|
91
|
+
expect(examples.length).toBe(27);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should generate examples with required fields', () => {
|
|
95
|
+
const examples = gen.generate();
|
|
96
|
+
for (const ex of examples) {
|
|
97
|
+
expect(ex.id).toBeDefined();
|
|
98
|
+
expect(ex.id.length).toBeGreaterThan(0);
|
|
99
|
+
expect(ex.instruction).toBeDefined();
|
|
100
|
+
expect(ex.instruction.length).toBeGreaterThan(0);
|
|
101
|
+
expect(ex.response).toBeDefined();
|
|
102
|
+
expect(ex.response.length).toBeGreaterThan(0);
|
|
103
|
+
expect(ex.context).toBeDefined();
|
|
104
|
+
expect(ex.context.length).toBeGreaterThan(0);
|
|
105
|
+
expect(ex.relationshipType).toBeDefined();
|
|
106
|
+
expect(ex.difficulty).toBeDefined();
|
|
107
|
+
expect(ex.tags).toBeDefined();
|
|
108
|
+
expect(ex.tags.length).toBeGreaterThan(0);
|
|
109
|
+
expect(typeof ex.isPositive).toBe('boolean');
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should produce unique IDs for all examples', () => {
|
|
114
|
+
const examples = gen.generate();
|
|
115
|
+
const ids = examples.map((ex) => ex.id);
|
|
116
|
+
const uniqueIds = new Set(ids);
|
|
117
|
+
expect(uniqueIds.size).toBe(ids.length);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should produce valid HoloScript in context', () => {
|
|
121
|
+
const examples = gen.generate();
|
|
122
|
+
for (const ex of examples) {
|
|
123
|
+
expect(ex.context).toContain('composition "SpatialScene"');
|
|
124
|
+
expect(ex.context).toContain('{');
|
|
125
|
+
expect(ex.context).toContain('}');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
// Reproducibility (seeded RNG)
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
|
|
134
|
+
describe('reproducibility', () => {
|
|
135
|
+
it('should produce identical output with same seed', () => {
|
|
136
|
+
const gen1 = createSeededGenerator({ examplesPerCategory: 5 });
|
|
137
|
+
const gen2 = createSeededGenerator({ examplesPerCategory: 5 });
|
|
138
|
+
|
|
139
|
+
const examples1 = gen1.generate();
|
|
140
|
+
const examples2 = gen2.generate();
|
|
141
|
+
|
|
142
|
+
expect(examples1.length).toBe(examples2.length);
|
|
143
|
+
for (let i = 0; i < examples1.length; i++) {
|
|
144
|
+
expect(examples1[i].instruction).toBe(examples2[i].instruction);
|
|
145
|
+
expect(examples1[i].response).toBe(examples2[i].response);
|
|
146
|
+
expect(examples1[i].context).toBe(examples2[i].context);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should produce different output with different seeds', () => {
|
|
151
|
+
const gen1 = new SpatialTrainingDataGenerator({ seed: 1, examplesPerCategory: 5 });
|
|
152
|
+
const gen2 = new SpatialTrainingDataGenerator({ seed: 2, examplesPerCategory: 5 });
|
|
153
|
+
|
|
154
|
+
const examples1 = gen1.generate();
|
|
155
|
+
const examples2 = gen2.generate();
|
|
156
|
+
|
|
157
|
+
// At least some examples should differ
|
|
158
|
+
let diffCount = 0;
|
|
159
|
+
for (let i = 0; i < Math.min(examples1.length, examples2.length); i++) {
|
|
160
|
+
if (examples1[i].instruction !== examples2[i].instruction) {
|
|
161
|
+
diffCount++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
expect(diffCount).toBeGreaterThan(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should allow reseed for fresh generation', () => {
|
|
168
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
169
|
+
const first = gen.generate();
|
|
170
|
+
|
|
171
|
+
gen.reseed(FIXED_SEED);
|
|
172
|
+
const second = gen.generate();
|
|
173
|
+
|
|
174
|
+
expect(first.length).toBe(second.length);
|
|
175
|
+
for (let i = 0; i < first.length; i++) {
|
|
176
|
+
expect(first[i].instruction).toBe(second[i].instruction);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// Relationship type coverage
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
describe('relationship types', () => {
|
|
186
|
+
it('should generate spatial_adjacent examples', () => {
|
|
187
|
+
const gen = createSeededGenerator({
|
|
188
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
189
|
+
examplesPerCategory: 5,
|
|
190
|
+
});
|
|
191
|
+
const examples = gen.generate();
|
|
192
|
+
|
|
193
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
194
|
+
for (const ex of examples) {
|
|
195
|
+
expect(ex.relationshipType).toBe('spatial_adjacent');
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('should generate spatial_contains examples', () => {
|
|
200
|
+
const gen = createSeededGenerator({
|
|
201
|
+
relationshipTypes: ['spatial_contains'],
|
|
202
|
+
examplesPerCategory: 5,
|
|
203
|
+
});
|
|
204
|
+
const examples = gen.generate();
|
|
205
|
+
|
|
206
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
207
|
+
for (const ex of examples) {
|
|
208
|
+
expect(ex.relationshipType).toBe('spatial_contains');
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('should generate spatial_reachable examples', () => {
|
|
213
|
+
const gen = createSeededGenerator({
|
|
214
|
+
relationshipTypes: ['spatial_reachable'],
|
|
215
|
+
examplesPerCategory: 5,
|
|
216
|
+
});
|
|
217
|
+
const examples = gen.generate();
|
|
218
|
+
|
|
219
|
+
expect(examples.length).toBeGreaterThan(0);
|
|
220
|
+
for (const ex of examples) {
|
|
221
|
+
expect(ex.relationshipType).toBe('spatial_reachable');
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('should include spatial constraint traits in HoloScript context', () => {
|
|
226
|
+
const gen = createSeededGenerator({ examplesPerCategory: 5 });
|
|
227
|
+
const examples = gen.generate();
|
|
228
|
+
|
|
229
|
+
const adjacentExamples = examples.filter((e) => e.relationshipType === 'spatial_adjacent');
|
|
230
|
+
for (const ex of adjacentExamples) {
|
|
231
|
+
expect(ex.context).toContain('@spatial_adjacent');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const containsExamples = examples.filter((e) => e.relationshipType === 'spatial_contains');
|
|
235
|
+
for (const ex of containsExamples) {
|
|
236
|
+
expect(ex.context).toContain('@spatial_contains');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const reachableExamples = examples.filter((e) => e.relationshipType === 'spatial_reachable');
|
|
240
|
+
for (const ex of reachableExamples) {
|
|
241
|
+
expect(ex.context).toContain('@spatial_reachable');
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should generate for specific relationship type', () => {
|
|
246
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
247
|
+
const examples = gen.generateForRelationship('spatial_contains');
|
|
248
|
+
|
|
249
|
+
expect(examples.length).toBe(9); // 3 difficulties x 3 examples
|
|
250
|
+
for (const ex of examples) {
|
|
251
|
+
expect(ex.relationshipType).toBe('spatial_contains');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Positive / Negative examples
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
|
|
260
|
+
describe('positive and negative examples', () => {
|
|
261
|
+
it('should generate both positive and negative examples', () => {
|
|
262
|
+
const gen = createSeededGenerator({ examplesPerCategory: 20 });
|
|
263
|
+
const examples = gen.generate();
|
|
264
|
+
|
|
265
|
+
const positive = examples.filter((e) => e.isPositive);
|
|
266
|
+
const negative = examples.filter((e) => !e.isPositive);
|
|
267
|
+
|
|
268
|
+
expect(positive.length).toBeGreaterThan(0);
|
|
269
|
+
expect(negative.length).toBeGreaterThan(0);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should respect positiveRatio configuration', () => {
|
|
273
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
274
|
+
seed: FIXED_SEED,
|
|
275
|
+
examplesPerCategory: 100,
|
|
276
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
277
|
+
difficultyLevels: ['basic'],
|
|
278
|
+
positiveRatio: 0.7,
|
|
279
|
+
});
|
|
280
|
+
const examples = gen.generate();
|
|
281
|
+
|
|
282
|
+
const positiveCount = examples.filter((e) => e.isPositive).length;
|
|
283
|
+
const ratio = positiveCount / examples.length;
|
|
284
|
+
|
|
285
|
+
// Allow some variance due to randomness
|
|
286
|
+
expect(ratio).toBeGreaterThan(0.5);
|
|
287
|
+
expect(ratio).toBeLessThan(0.9);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should generate positive adjacent examples with objects within maxDistance', () => {
|
|
291
|
+
const gen = createSeededGenerator({
|
|
292
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
293
|
+
examplesPerCategory: 20,
|
|
294
|
+
difficultyLevels: ['basic'],
|
|
295
|
+
positiveRatio: 1.0,
|
|
296
|
+
});
|
|
297
|
+
const examples = gen.generate();
|
|
298
|
+
|
|
299
|
+
for (const ex of examples) {
|
|
300
|
+
expect(ex.isPositive).toBe(true);
|
|
301
|
+
// Positive adjacent responses should indicate objects are close enough
|
|
302
|
+
expect(ex.response.toLowerCase()).toMatch(
|
|
303
|
+
/yes|satisf|pass|within|close|proper|no violation/i
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('should generate negative adjacent examples with objects beyond maxDistance', () => {
|
|
309
|
+
const gen = createSeededGenerator({
|
|
310
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
311
|
+
examplesPerCategory: 20,
|
|
312
|
+
difficultyLevels: ['basic'],
|
|
313
|
+
positiveRatio: 0.0,
|
|
314
|
+
});
|
|
315
|
+
const examples = gen.generate();
|
|
316
|
+
|
|
317
|
+
for (const ex of examples) {
|
|
318
|
+
expect(ex.isPositive).toBe(false);
|
|
319
|
+
// Negative adjacent responses should indicate objects are too far
|
|
320
|
+
expect(ex.response.toLowerCase()).toMatch(
|
|
321
|
+
/no|exceed|fail|violat|not|outside|too far|improper|break/i
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should generate positive contains examples with objects inside bounds', () => {
|
|
327
|
+
const gen = createSeededGenerator({
|
|
328
|
+
relationshipTypes: ['spatial_contains'],
|
|
329
|
+
examplesPerCategory: 10,
|
|
330
|
+
difficultyLevels: ['basic'],
|
|
331
|
+
positiveRatio: 1.0,
|
|
332
|
+
});
|
|
333
|
+
const examples = gen.generate();
|
|
334
|
+
|
|
335
|
+
for (const ex of examples) {
|
|
336
|
+
expect(ex.isPositive).toBe(true);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('should generate negative contains examples with objects outside bounds', () => {
|
|
341
|
+
const gen = createSeededGenerator({
|
|
342
|
+
relationshipTypes: ['spatial_contains'],
|
|
343
|
+
examplesPerCategory: 10,
|
|
344
|
+
difficultyLevels: ['basic'],
|
|
345
|
+
positiveRatio: 0.0,
|
|
346
|
+
});
|
|
347
|
+
const examples = gen.generate();
|
|
348
|
+
|
|
349
|
+
for (const ex of examples) {
|
|
350
|
+
expect(ex.isPositive).toBe(false);
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should generate positive reachable examples with clear paths', () => {
|
|
355
|
+
const gen = createSeededGenerator({
|
|
356
|
+
relationshipTypes: ['spatial_reachable'],
|
|
357
|
+
examplesPerCategory: 10,
|
|
358
|
+
difficultyLevels: ['basic'],
|
|
359
|
+
positiveRatio: 1.0,
|
|
360
|
+
});
|
|
361
|
+
const examples = gen.generate();
|
|
362
|
+
|
|
363
|
+
for (const ex of examples) {
|
|
364
|
+
expect(ex.isPositive).toBe(true);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('should generate negative reachable examples with blocked paths', () => {
|
|
369
|
+
const gen = createSeededGenerator({
|
|
370
|
+
relationshipTypes: ['spatial_reachable'],
|
|
371
|
+
examplesPerCategory: 10,
|
|
372
|
+
difficultyLevels: ['intermediate', 'advanced'],
|
|
373
|
+
positiveRatio: 0.0,
|
|
374
|
+
});
|
|
375
|
+
const examples = gen.generate();
|
|
376
|
+
|
|
377
|
+
for (const ex of examples) {
|
|
378
|
+
expect(ex.isPositive).toBe(false);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
it('should tag positive and negative examples correctly', () => {
|
|
383
|
+
const gen = createSeededGenerator({ examplesPerCategory: 10 });
|
|
384
|
+
const examples = gen.generate();
|
|
385
|
+
|
|
386
|
+
for (const ex of examples) {
|
|
387
|
+
if (ex.isPositive) {
|
|
388
|
+
expect(ex.tags).toContain('positive');
|
|
389
|
+
expect(ex.tags).not.toContain('negative');
|
|
390
|
+
} else {
|
|
391
|
+
expect(ex.tags).toContain('negative');
|
|
392
|
+
expect(ex.tags).not.toContain('positive');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
// Difficulty levels
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
describe('difficulty levels', () => {
|
|
403
|
+
it('should generate basic difficulty examples (2 objects)', () => {
|
|
404
|
+
const gen = createSeededGenerator({
|
|
405
|
+
difficultyLevels: ['basic'],
|
|
406
|
+
examplesPerCategory: 5,
|
|
407
|
+
});
|
|
408
|
+
const examples = gen.generate();
|
|
409
|
+
|
|
410
|
+
for (const ex of examples) {
|
|
411
|
+
expect(ex.difficulty).toBe('basic');
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('should generate intermediate difficulty examples (3-5 objects)', () => {
|
|
416
|
+
const gen = createSeededGenerator({
|
|
417
|
+
difficultyLevels: ['intermediate'],
|
|
418
|
+
examplesPerCategory: 5,
|
|
419
|
+
});
|
|
420
|
+
const examples = gen.generate();
|
|
421
|
+
|
|
422
|
+
for (const ex of examples) {
|
|
423
|
+
expect(ex.difficulty).toBe('intermediate');
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should generate advanced difficulty examples (6+ objects)', () => {
|
|
428
|
+
const gen = createSeededGenerator({
|
|
429
|
+
difficultyLevels: ['advanced'],
|
|
430
|
+
examplesPerCategory: 5,
|
|
431
|
+
});
|
|
432
|
+
const examples = gen.generate();
|
|
433
|
+
|
|
434
|
+
for (const ex of examples) {
|
|
435
|
+
expect(ex.difficulty).toBe('advanced');
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should generate for specific difficulty level', () => {
|
|
440
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
441
|
+
const examples = gen.generateForDifficulty('intermediate');
|
|
442
|
+
|
|
443
|
+
expect(examples.length).toBe(9); // 3 rel types x 3 examples
|
|
444
|
+
for (const ex of examples) {
|
|
445
|
+
expect(ex.difficulty).toBe('intermediate');
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
it('should include more objects in advanced scenes', () => {
|
|
450
|
+
const gen = createSeededGenerator({
|
|
451
|
+
difficultyLevels: ['advanced'],
|
|
452
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
453
|
+
examplesPerCategory: 5,
|
|
454
|
+
});
|
|
455
|
+
const examples = gen.generate();
|
|
456
|
+
|
|
457
|
+
for (const ex of examples) {
|
|
458
|
+
// Advanced scenes should have more object declarations
|
|
459
|
+
const objectMatches = ex.context.match(/object\s+"/g);
|
|
460
|
+
expect(objectMatches).toBeDefined();
|
|
461
|
+
expect(objectMatches!.length).toBeGreaterThanOrEqual(4);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should have basic scenes with minimal objects', () => {
|
|
466
|
+
const gen = createSeededGenerator({
|
|
467
|
+
difficultyLevels: ['basic'],
|
|
468
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
469
|
+
examplesPerCategory: 5,
|
|
470
|
+
});
|
|
471
|
+
const examples = gen.generate();
|
|
472
|
+
|
|
473
|
+
for (const ex of examples) {
|
|
474
|
+
const objectMatches = ex.context.match(/object\s+"/g);
|
|
475
|
+
expect(objectMatches).toBeDefined();
|
|
476
|
+
expect(objectMatches!.length).toBe(2);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('should include nested containment in advanced contains scenes', () => {
|
|
481
|
+
const gen = createSeededGenerator({
|
|
482
|
+
difficultyLevels: ['advanced'],
|
|
483
|
+
relationshipTypes: ['spatial_contains'],
|
|
484
|
+
examplesPerCategory: 5,
|
|
485
|
+
});
|
|
486
|
+
const examples = gen.generate();
|
|
487
|
+
|
|
488
|
+
// At least some advanced contains scenes should have nested zones
|
|
489
|
+
const scenesWithMultipleZones = examples.filter((ex) => {
|
|
490
|
+
const zoneMatches = ex.context.match(/zone\s+"/g);
|
|
491
|
+
return zoneMatches && zoneMatches.length >= 2;
|
|
492
|
+
});
|
|
493
|
+
expect(scenesWithMultipleZones.length).toBeGreaterThan(0);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should include obstacles in advanced reachable scenes', () => {
|
|
497
|
+
const gen = createSeededGenerator({
|
|
498
|
+
difficultyLevels: ['advanced'],
|
|
499
|
+
relationshipTypes: ['spatial_reachable'],
|
|
500
|
+
examplesPerCategory: 5,
|
|
501
|
+
});
|
|
502
|
+
const examples = gen.generate();
|
|
503
|
+
|
|
504
|
+
// Advanced reachable scenes should have obstacle objects
|
|
505
|
+
const scenesWithObstacles = examples.filter((ex) => {
|
|
506
|
+
return ex.context.includes('@static') || ex.context.includes('@collidable');
|
|
507
|
+
});
|
|
508
|
+
expect(scenesWithObstacles.length).toBeGreaterThan(0);
|
|
509
|
+
});
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Template diversity (G.002 mandate: 10+ templates)
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
describe('template diversity (G.002 compliance)', () => {
|
|
517
|
+
it('should use 10+ templates for spatial_adjacent', () => {
|
|
518
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
519
|
+
seed: FIXED_SEED,
|
|
520
|
+
examplesPerCategory: 50,
|
|
521
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
522
|
+
difficultyLevels: ['basic'],
|
|
523
|
+
});
|
|
524
|
+
const examples = gen.generate();
|
|
525
|
+
|
|
526
|
+
const templateTags = new Set<string>();
|
|
527
|
+
for (const ex of examples) {
|
|
528
|
+
const tplTag = ex.tags.find((t) => t.startsWith('template:'));
|
|
529
|
+
if (tplTag) templateTags.add(tplTag);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
expect(templateTags.size).toBeGreaterThanOrEqual(10);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('should use 10+ templates for spatial_contains', () => {
|
|
536
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
537
|
+
seed: FIXED_SEED,
|
|
538
|
+
examplesPerCategory: 50,
|
|
539
|
+
relationshipTypes: ['spatial_contains'],
|
|
540
|
+
difficultyLevels: ['basic'],
|
|
541
|
+
});
|
|
542
|
+
const examples = gen.generate();
|
|
543
|
+
|
|
544
|
+
const templateTags = new Set<string>();
|
|
545
|
+
for (const ex of examples) {
|
|
546
|
+
const tplTag = ex.tags.find((t) => t.startsWith('template:'));
|
|
547
|
+
if (tplTag) templateTags.add(tplTag);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
expect(templateTags.size).toBeGreaterThanOrEqual(10);
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should use 10+ templates for spatial_reachable', () => {
|
|
554
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
555
|
+
seed: FIXED_SEED,
|
|
556
|
+
examplesPerCategory: 50,
|
|
557
|
+
relationshipTypes: ['spatial_reachable'],
|
|
558
|
+
difficultyLevels: ['basic'],
|
|
559
|
+
});
|
|
560
|
+
const examples = gen.generate();
|
|
561
|
+
|
|
562
|
+
const templateTags = new Set<string>();
|
|
563
|
+
for (const ex of examples) {
|
|
564
|
+
const tplTag = ex.tags.find((t) => t.startsWith('template:'));
|
|
565
|
+
if (tplTag) templateTags.add(tplTag);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
expect(templateTags.size).toBeGreaterThanOrEqual(10);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should produce diverse instruction texts (no single template dominates)', () => {
|
|
572
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
573
|
+
seed: FIXED_SEED,
|
|
574
|
+
examplesPerCategory: 30,
|
|
575
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
576
|
+
difficultyLevels: ['basic'],
|
|
577
|
+
});
|
|
578
|
+
const examples = gen.generate();
|
|
579
|
+
|
|
580
|
+
// Count template usage
|
|
581
|
+
const templateCounts = new Map<string, number>();
|
|
582
|
+
for (const ex of examples) {
|
|
583
|
+
const tplTag = ex.tags.find((t) => t.startsWith('template:')) ?? 'unknown';
|
|
584
|
+
templateCounts.set(tplTag, (templateCounts.get(tplTag) ?? 0) + 1);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// No single template should account for more than 40% of examples
|
|
588
|
+
for (const [, count] of templateCounts) {
|
|
589
|
+
expect(count / examples.length).toBeLessThan(0.4);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
// HoloScript source generation
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
describe('HoloScript source generation', () => {
|
|
599
|
+
it('should generate valid composition blocks', () => {
|
|
600
|
+
const gen = createSeededGenerator({ examplesPerCategory: 5 });
|
|
601
|
+
const examples = gen.generate();
|
|
602
|
+
|
|
603
|
+
for (const ex of examples) {
|
|
604
|
+
expect(ex.context).toMatch(/^composition\s+"SpatialScene"\s+\{/);
|
|
605
|
+
expect(ex.context.endsWith('}')).toBe(true);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('should include object positions in HoloScript', () => {
|
|
610
|
+
const gen = createSeededGenerator({ examplesPerCategory: 5 });
|
|
611
|
+
const examples = gen.generate();
|
|
612
|
+
|
|
613
|
+
for (const ex of examples) {
|
|
614
|
+
expect(ex.context).toMatch(/position:\s+\[[\d\-.,\s]+\]/);
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('should include geometry types in object blocks', () => {
|
|
619
|
+
const gen = createSeededGenerator({
|
|
620
|
+
examplesPerCategory: 5,
|
|
621
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
622
|
+
});
|
|
623
|
+
const examples = gen.generate();
|
|
624
|
+
|
|
625
|
+
for (const ex of examples) {
|
|
626
|
+
expect(ex.context).toMatch(/geometry:\s+"\w+"/);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('should include spatial_adjacent trait with parameters', () => {
|
|
631
|
+
const gen = createSeededGenerator({
|
|
632
|
+
examplesPerCategory: 5,
|
|
633
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
634
|
+
});
|
|
635
|
+
const examples = gen.generate();
|
|
636
|
+
|
|
637
|
+
for (const ex of examples) {
|
|
638
|
+
expect(ex.context).toMatch(
|
|
639
|
+
/@spatial_adjacent\(target:\s+"[^"]+",\s+maxDistance:\s+[\d.]+m\)/
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('should include spatial_contains trait with parameters', () => {
|
|
645
|
+
const gen = createSeededGenerator({
|
|
646
|
+
examplesPerCategory: 5,
|
|
647
|
+
relationshipTypes: ['spatial_contains'],
|
|
648
|
+
});
|
|
649
|
+
const examples = gen.generate();
|
|
650
|
+
|
|
651
|
+
for (const ex of examples) {
|
|
652
|
+
expect(ex.context).toMatch(/@spatial_contains\(target:\s+"[^"]+"/);
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
it('should include spatial_reachable trait with parameters', () => {
|
|
657
|
+
const gen = createSeededGenerator({
|
|
658
|
+
examplesPerCategory: 5,
|
|
659
|
+
relationshipTypes: ['spatial_reachable'],
|
|
660
|
+
});
|
|
661
|
+
const examples = gen.generate();
|
|
662
|
+
|
|
663
|
+
for (const ex of examples) {
|
|
664
|
+
expect(ex.context).toMatch(/@spatial_reachable\(target:\s+"[^"]+"/);
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('should include zone blocks for containment scenes', () => {
|
|
669
|
+
const gen = createSeededGenerator({
|
|
670
|
+
examplesPerCategory: 5,
|
|
671
|
+
relationshipTypes: ['spatial_contains'],
|
|
672
|
+
});
|
|
673
|
+
const examples = gen.generate();
|
|
674
|
+
|
|
675
|
+
for (const ex of examples) {
|
|
676
|
+
expect(ex.context).toContain('zone "');
|
|
677
|
+
expect(ex.context).toContain('shape: "box"');
|
|
678
|
+
expect(ex.context).toContain('size:');
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it('should include obstacle markers in reachable scenes', () => {
|
|
683
|
+
const gen = createSeededGenerator({
|
|
684
|
+
examplesPerCategory: 10,
|
|
685
|
+
relationshipTypes: ['spatial_reachable'],
|
|
686
|
+
difficultyLevels: ['intermediate', 'advanced'],
|
|
687
|
+
});
|
|
688
|
+
const examples = gen.generate();
|
|
689
|
+
|
|
690
|
+
const withObstacles = examples.filter(
|
|
691
|
+
(ex) => ex.context.includes('@static') || ex.context.includes('@collidable')
|
|
692
|
+
);
|
|
693
|
+
expect(withObstacles.length).toBeGreaterThan(0);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
// ---------------------------------------------------------------------------
|
|
698
|
+
// Scene generation
|
|
699
|
+
// ---------------------------------------------------------------------------
|
|
700
|
+
|
|
701
|
+
describe('scene generation', () => {
|
|
702
|
+
it('should generate adjacent scenes', () => {
|
|
703
|
+
const gen = createSeededGenerator();
|
|
704
|
+
const scene = gen.generateScene('spatial_adjacent', 'basic', true);
|
|
705
|
+
|
|
706
|
+
expect(scene.name).toContain('Adjacent');
|
|
707
|
+
expect(scene.objects.length).toBe(2);
|
|
708
|
+
expect(scene.relationships.length).toBe(1);
|
|
709
|
+
expect(scene.relationships[0].type).toBe('spatial_adjacent');
|
|
710
|
+
expect(scene.difficulty).toBe('basic');
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
it('should generate contains scenes', () => {
|
|
714
|
+
const gen = createSeededGenerator();
|
|
715
|
+
const scene = gen.generateScene('spatial_contains', 'basic', true);
|
|
716
|
+
|
|
717
|
+
expect(scene.name).toContain('Contains');
|
|
718
|
+
expect(scene.objects.length).toBeGreaterThanOrEqual(2);
|
|
719
|
+
expect(scene.relationships.length).toBe(1);
|
|
720
|
+
expect(scene.relationships[0].type).toBe('spatial_contains');
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it('should generate reachable scenes', () => {
|
|
724
|
+
const gen = createSeededGenerator();
|
|
725
|
+
const scene = gen.generateScene('spatial_reachable', 'basic', true);
|
|
726
|
+
|
|
727
|
+
expect(scene.name).toContain('Reachable');
|
|
728
|
+
expect(scene.objects.length).toBeGreaterThanOrEqual(2);
|
|
729
|
+
expect(scene.relationships.length).toBe(1);
|
|
730
|
+
expect(scene.relationships[0].type).toBe('spatial_reachable');
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
it('should include HoloScript source in scene', () => {
|
|
734
|
+
const gen = createSeededGenerator();
|
|
735
|
+
const scene = gen.generateScene('spatial_adjacent', 'basic', true);
|
|
736
|
+
|
|
737
|
+
expect(scene.holoScriptSource).toBeDefined();
|
|
738
|
+
expect(scene.holoScriptSource.length).toBeGreaterThan(0);
|
|
739
|
+
expect(scene.holoScriptSource).toContain('composition');
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it('should generate positive adjacent scenes with satisfied constraint', () => {
|
|
743
|
+
const gen = createSeededGenerator();
|
|
744
|
+
const scene = gen.generateScene('spatial_adjacent', 'basic', true);
|
|
745
|
+
|
|
746
|
+
expect(scene.relationships[0].satisfied).toBe(true);
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
it('should generate negative adjacent scenes with violated constraint', () => {
|
|
750
|
+
const gen = createSeededGenerator();
|
|
751
|
+
const scene = gen.generateScene('spatial_adjacent', 'basic', false);
|
|
752
|
+
|
|
753
|
+
expect(scene.relationships[0].satisfied).toBe(false);
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
it('should generate positive contains scenes with object inside bounds', () => {
|
|
757
|
+
const gen = createSeededGenerator();
|
|
758
|
+
const scene = gen.generateScene('spatial_contains', 'basic', true);
|
|
759
|
+
|
|
760
|
+
expect(scene.relationships[0].satisfied).toBe(true);
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('should generate negative contains scenes with object outside bounds', () => {
|
|
764
|
+
const gen = createSeededGenerator();
|
|
765
|
+
const scene = gen.generateScene('spatial_contains', 'basic', false);
|
|
766
|
+
|
|
767
|
+
expect(scene.relationships[0].satisfied).toBe(false);
|
|
768
|
+
});
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// ---------------------------------------------------------------------------
|
|
772
|
+
// JSONL export
|
|
773
|
+
// ---------------------------------------------------------------------------
|
|
774
|
+
|
|
775
|
+
describe('JSONL export', () => {
|
|
776
|
+
it('should export valid JSONL', () => {
|
|
777
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
778
|
+
const examples = gen.generate();
|
|
779
|
+
const jsonl = gen.exportJSONL(examples);
|
|
780
|
+
|
|
781
|
+
expect(jsonl).toBeDefined();
|
|
782
|
+
expect(jsonl.length).toBeGreaterThan(0);
|
|
783
|
+
|
|
784
|
+
const lines = jsonl.split('\n');
|
|
785
|
+
expect(lines.length).toBe(examples.length);
|
|
786
|
+
|
|
787
|
+
for (const line of lines) {
|
|
788
|
+
expect(() => JSON.parse(line)).not.toThrow();
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it('should include instruction and response fields in each JSONL entry', () => {
|
|
793
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
794
|
+
const examples = gen.generate();
|
|
795
|
+
const jsonl = gen.exportJSONL(examples);
|
|
796
|
+
|
|
797
|
+
const lines = jsonl.split('\n');
|
|
798
|
+
for (const line of lines) {
|
|
799
|
+
const entry: SpatialTrainingJSONLEntry = JSON.parse(line);
|
|
800
|
+
expect(entry.instruction).toBeDefined();
|
|
801
|
+
expect(entry.instruction.length).toBeGreaterThan(0);
|
|
802
|
+
expect(entry.response).toBeDefined();
|
|
803
|
+
expect(entry.response.length).toBeGreaterThan(0);
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
|
|
807
|
+
it('should include metadata in JSONL entries', () => {
|
|
808
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
809
|
+
const examples = gen.generate();
|
|
810
|
+
const jsonl = gen.exportJSONL(examples);
|
|
811
|
+
|
|
812
|
+
const lines = jsonl.split('\n');
|
|
813
|
+
for (const line of lines) {
|
|
814
|
+
const entry: SpatialTrainingJSONLEntry = JSON.parse(line);
|
|
815
|
+
expect(entry.metadata).toBeDefined();
|
|
816
|
+
expect(entry.metadata.id).toBeDefined();
|
|
817
|
+
expect(entry.metadata.relationship_type).toBeDefined();
|
|
818
|
+
expect(typeof entry.metadata.is_positive).toBe('boolean');
|
|
819
|
+
expect(entry.metadata.difficulty).toBeDefined();
|
|
820
|
+
expect(entry.metadata.tags).toBeDefined();
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
it('should include HoloScript context when includeContext is true', () => {
|
|
825
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
826
|
+
seed: FIXED_SEED,
|
|
827
|
+
examplesPerCategory: 3,
|
|
828
|
+
includeContext: true,
|
|
829
|
+
});
|
|
830
|
+
const examples = gen.generate();
|
|
831
|
+
const jsonl = gen.exportJSONL(examples);
|
|
832
|
+
|
|
833
|
+
const lines = jsonl.split('\n');
|
|
834
|
+
for (const line of lines) {
|
|
835
|
+
const entry: SpatialTrainingJSONLEntry = JSON.parse(line);
|
|
836
|
+
expect(entry.instruction).toContain('HoloScript Scene:');
|
|
837
|
+
expect(entry.instruction).toContain('```holoscript');
|
|
838
|
+
}
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
it('should exclude HoloScript context when includeContext is false', () => {
|
|
842
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
843
|
+
seed: FIXED_SEED,
|
|
844
|
+
examplesPerCategory: 3,
|
|
845
|
+
includeContext: false,
|
|
846
|
+
});
|
|
847
|
+
const examples = gen.generate();
|
|
848
|
+
const jsonl = gen.exportJSONL(examples);
|
|
849
|
+
|
|
850
|
+
const lines = jsonl.split('\n');
|
|
851
|
+
for (const line of lines) {
|
|
852
|
+
const entry: SpatialTrainingJSONLEntry = JSON.parse(line);
|
|
853
|
+
expect(entry.instruction).not.toContain('HoloScript Scene:');
|
|
854
|
+
}
|
|
855
|
+
});
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
// ---------------------------------------------------------------------------
|
|
859
|
+
// JSON export
|
|
860
|
+
// ---------------------------------------------------------------------------
|
|
861
|
+
|
|
862
|
+
describe('JSON export', () => {
|
|
863
|
+
it('should export valid JSON', () => {
|
|
864
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
865
|
+
const examples = gen.generate();
|
|
866
|
+
const json = gen.exportJSON(examples);
|
|
867
|
+
|
|
868
|
+
expect(json).toBeDefined();
|
|
869
|
+
const parsed = JSON.parse(json);
|
|
870
|
+
expect(Array.isArray(parsed)).toBe(true);
|
|
871
|
+
expect(parsed.length).toBe(examples.length);
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it('should preserve all example fields in JSON export', () => {
|
|
875
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
876
|
+
const examples = gen.generate();
|
|
877
|
+
const json = gen.exportJSON(examples);
|
|
878
|
+
const parsed: SpatialTrainingExample[] = JSON.parse(json);
|
|
879
|
+
|
|
880
|
+
for (let i = 0; i < examples.length; i++) {
|
|
881
|
+
expect(parsed[i].id).toBe(examples[i].id);
|
|
882
|
+
expect(parsed[i].instruction).toBe(examples[i].instruction);
|
|
883
|
+
expect(parsed[i].response).toBe(examples[i].response);
|
|
884
|
+
expect(parsed[i].relationshipType).toBe(examples[i].relationshipType);
|
|
885
|
+
expect(parsed[i].isPositive).toBe(examples[i].isPositive);
|
|
886
|
+
expect(parsed[i].difficulty).toBe(examples[i].difficulty);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// ---------------------------------------------------------------------------
|
|
892
|
+
// Statistics
|
|
893
|
+
// ---------------------------------------------------------------------------
|
|
894
|
+
|
|
895
|
+
describe('getStats()', () => {
|
|
896
|
+
it('should return correct total count', () => {
|
|
897
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
898
|
+
const examples = gen.generate();
|
|
899
|
+
const stats = gen.getStats(examples);
|
|
900
|
+
|
|
901
|
+
expect(stats.totalExamples).toBe(examples.length);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it('should break down by relationship type', () => {
|
|
905
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
906
|
+
const examples = gen.generate();
|
|
907
|
+
const stats = gen.getStats(examples);
|
|
908
|
+
|
|
909
|
+
expect(stats.byRelationship.spatial_adjacent).toBeGreaterThan(0);
|
|
910
|
+
expect(stats.byRelationship.spatial_contains).toBeGreaterThan(0);
|
|
911
|
+
expect(stats.byRelationship.spatial_reachable).toBeGreaterThan(0);
|
|
912
|
+
|
|
913
|
+
const relTotal =
|
|
914
|
+
stats.byRelationship.spatial_adjacent +
|
|
915
|
+
stats.byRelationship.spatial_contains +
|
|
916
|
+
stats.byRelationship.spatial_reachable;
|
|
917
|
+
expect(relTotal).toBe(stats.totalExamples);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it('should break down by difficulty', () => {
|
|
921
|
+
const gen = createSeededGenerator({ examplesPerCategory: 3 });
|
|
922
|
+
const examples = gen.generate();
|
|
923
|
+
const stats = gen.getStats(examples);
|
|
924
|
+
|
|
925
|
+
expect(stats.byDifficulty.basic).toBeGreaterThan(0);
|
|
926
|
+
expect(stats.byDifficulty.intermediate).toBeGreaterThan(0);
|
|
927
|
+
expect(stats.byDifficulty.advanced).toBeGreaterThan(0);
|
|
928
|
+
|
|
929
|
+
const diffTotal =
|
|
930
|
+
stats.byDifficulty.basic + stats.byDifficulty.intermediate + stats.byDifficulty.advanced;
|
|
931
|
+
expect(diffTotal).toBe(stats.totalExamples);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
it('should count positive and negative examples', () => {
|
|
935
|
+
const gen = createSeededGenerator({ examplesPerCategory: 10 });
|
|
936
|
+
const examples = gen.generate();
|
|
937
|
+
const stats = gen.getStats(examples);
|
|
938
|
+
|
|
939
|
+
expect(stats.positiveCount + stats.negativeCount).toBe(stats.totalExamples);
|
|
940
|
+
expect(stats.positiveCount).toBeGreaterThan(0);
|
|
941
|
+
expect(stats.negativeCount).toBeGreaterThan(0);
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
it('should track unique templates used', () => {
|
|
945
|
+
const gen = createSeededGenerator({ examplesPerCategory: 20 });
|
|
946
|
+
const examples = gen.generate();
|
|
947
|
+
const stats = gen.getStats(examples);
|
|
948
|
+
|
|
949
|
+
expect(stats.uniqueTemplatesUsed).toBeGreaterThan(0);
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
// ---------------------------------------------------------------------------
|
|
954
|
+
// Randomized scene parameters
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
|
|
957
|
+
describe('randomized scene parameters', () => {
|
|
958
|
+
it('should randomize object positions', () => {
|
|
959
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
960
|
+
seed: FIXED_SEED,
|
|
961
|
+
examplesPerCategory: 10,
|
|
962
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
963
|
+
difficultyLevels: ['basic'],
|
|
964
|
+
});
|
|
965
|
+
const examples = gen.generate();
|
|
966
|
+
|
|
967
|
+
// Extract positions from HoloScript source
|
|
968
|
+
const positions = new Set<string>();
|
|
969
|
+
for (const ex of examples) {
|
|
970
|
+
const matches = ex.context.match(/position:\s+\[([^\]]+)\]/g);
|
|
971
|
+
if (matches) {
|
|
972
|
+
for (const m of matches) {
|
|
973
|
+
positions.add(m);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Should have diverse positions (not all the same)
|
|
979
|
+
expect(positions.size).toBeGreaterThan(5);
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
it('should randomize object scales', () => {
|
|
983
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
984
|
+
seed: FIXED_SEED,
|
|
985
|
+
examplesPerCategory: 10,
|
|
986
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
987
|
+
difficultyLevels: ['intermediate'],
|
|
988
|
+
});
|
|
989
|
+
const examples = gen.generate();
|
|
990
|
+
|
|
991
|
+
const scales = new Set<string>();
|
|
992
|
+
for (const ex of examples) {
|
|
993
|
+
const matches = ex.context.match(/scale:\s+\[([^\]]+)\]/g);
|
|
994
|
+
if (matches) {
|
|
995
|
+
for (const m of matches) {
|
|
996
|
+
scales.add(m);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
expect(scales.size).toBeGreaterThan(3);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
it('should randomize maxDistance for adjacent constraints', () => {
|
|
1005
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
1006
|
+
seed: FIXED_SEED,
|
|
1007
|
+
examplesPerCategory: 10,
|
|
1008
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
1009
|
+
difficultyLevels: ['basic'],
|
|
1010
|
+
});
|
|
1011
|
+
const examples = gen.generate();
|
|
1012
|
+
|
|
1013
|
+
const distances = new Set<string>();
|
|
1014
|
+
for (const ex of examples) {
|
|
1015
|
+
const match = ex.context.match(/maxDistance:\s+([\d.]+)m/);
|
|
1016
|
+
if (match) {
|
|
1017
|
+
distances.add(match[1]);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
expect(distances.size).toBeGreaterThan(3);
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
it('should randomize container sizes for contains constraints', () => {
|
|
1025
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
1026
|
+
seed: FIXED_SEED,
|
|
1027
|
+
examplesPerCategory: 10,
|
|
1028
|
+
relationshipTypes: ['spatial_contains'],
|
|
1029
|
+
difficultyLevels: ['basic'],
|
|
1030
|
+
});
|
|
1031
|
+
const examples = gen.generate();
|
|
1032
|
+
|
|
1033
|
+
const sizes = new Set<string>();
|
|
1034
|
+
for (const ex of examples) {
|
|
1035
|
+
const match = ex.context.match(/size:\s+\[([^\]]+)\]/);
|
|
1036
|
+
if (match) {
|
|
1037
|
+
sizes.add(match[1]);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
expect(sizes.size).toBeGreaterThan(3);
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
it('should use diverse object names', () => {
|
|
1045
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
1046
|
+
seed: FIXED_SEED,
|
|
1047
|
+
examplesPerCategory: 10,
|
|
1048
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
1049
|
+
difficultyLevels: ['basic'],
|
|
1050
|
+
});
|
|
1051
|
+
const examples = gen.generate();
|
|
1052
|
+
|
|
1053
|
+
const names = new Set<string>();
|
|
1054
|
+
for (const ex of examples) {
|
|
1055
|
+
const matches = ex.context.match(/object\s+"([^"]+)"/g);
|
|
1056
|
+
if (matches) {
|
|
1057
|
+
for (const m of matches) {
|
|
1058
|
+
names.add(m);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
expect(names.size).toBeGreaterThan(5);
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
it('should use diverse geometry types', () => {
|
|
1067
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
1068
|
+
seed: FIXED_SEED,
|
|
1069
|
+
examplesPerCategory: 20,
|
|
1070
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
1071
|
+
difficultyLevels: ['basic'],
|
|
1072
|
+
});
|
|
1073
|
+
const examples = gen.generate();
|
|
1074
|
+
|
|
1075
|
+
const geoTypes = new Set<string>();
|
|
1076
|
+
for (const ex of examples) {
|
|
1077
|
+
const matches = ex.context.match(/geometry:\s+"(\w+)"/g);
|
|
1078
|
+
if (matches) {
|
|
1079
|
+
for (const m of matches) {
|
|
1080
|
+
geoTypes.add(m);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
expect(geoTypes.size).toBeGreaterThan(3);
|
|
1086
|
+
});
|
|
1087
|
+
});
|
|
1088
|
+
|
|
1089
|
+
// ---------------------------------------------------------------------------
|
|
1090
|
+
// Edge cases
|
|
1091
|
+
// ---------------------------------------------------------------------------
|
|
1092
|
+
|
|
1093
|
+
describe('edge cases', () => {
|
|
1094
|
+
it('should handle single relationship type', () => {
|
|
1095
|
+
const gen = createSeededGenerator({
|
|
1096
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
1097
|
+
examplesPerCategory: 3,
|
|
1098
|
+
});
|
|
1099
|
+
const examples = gen.generate();
|
|
1100
|
+
|
|
1101
|
+
expect(examples.length).toBe(9); // 1 type x 3 difficulties x 3 examples
|
|
1102
|
+
for (const ex of examples) {
|
|
1103
|
+
expect(ex.relationshipType).toBe('spatial_adjacent');
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
it('should handle single difficulty level', () => {
|
|
1108
|
+
const gen = createSeededGenerator({
|
|
1109
|
+
difficultyLevels: ['basic'],
|
|
1110
|
+
examplesPerCategory: 3,
|
|
1111
|
+
});
|
|
1112
|
+
const examples = gen.generate();
|
|
1113
|
+
|
|
1114
|
+
expect(examples.length).toBe(9); // 3 types x 1 difficulty x 3 examples
|
|
1115
|
+
for (const ex of examples) {
|
|
1116
|
+
expect(ex.difficulty).toBe('basic');
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
1119
|
+
|
|
1120
|
+
it('should handle examplesPerCategory = 1', () => {
|
|
1121
|
+
const gen = createSeededGenerator({ examplesPerCategory: 1 });
|
|
1122
|
+
const examples = gen.generate();
|
|
1123
|
+
|
|
1124
|
+
expect(examples.length).toBe(9); // 3 types x 3 difficulties x 1
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it('should handle positiveRatio = 1.0 (all positive)', () => {
|
|
1128
|
+
const gen = createSeededGenerator({
|
|
1129
|
+
positiveRatio: 1.0,
|
|
1130
|
+
examplesPerCategory: 10,
|
|
1131
|
+
});
|
|
1132
|
+
const examples = gen.generate();
|
|
1133
|
+
|
|
1134
|
+
for (const ex of examples) {
|
|
1135
|
+
expect(ex.isPositive).toBe(true);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
it('should handle positiveRatio = 0.0 (all negative)', () => {
|
|
1140
|
+
const gen = createSeededGenerator({
|
|
1141
|
+
positiveRatio: 0.0,
|
|
1142
|
+
examplesPerCategory: 10,
|
|
1143
|
+
});
|
|
1144
|
+
const examples = gen.generate();
|
|
1145
|
+
|
|
1146
|
+
for (const ex of examples) {
|
|
1147
|
+
expect(ex.isPositive).toBe(false);
|
|
1148
|
+
}
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
it('should handle large generation counts', () => {
|
|
1152
|
+
const gen = createSeededGenerator({
|
|
1153
|
+
examplesPerCategory: 100,
|
|
1154
|
+
relationshipTypes: ['spatial_adjacent'],
|
|
1155
|
+
difficultyLevels: ['basic'],
|
|
1156
|
+
});
|
|
1157
|
+
const examples = gen.generate();
|
|
1158
|
+
|
|
1159
|
+
expect(examples.length).toBe(100);
|
|
1160
|
+
// All IDs should still be unique
|
|
1161
|
+
const ids = new Set(examples.map((e) => e.id));
|
|
1162
|
+
expect(ids.size).toBe(100);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it('should not crash with zero examples per category', () => {
|
|
1166
|
+
const gen = createSeededGenerator({ examplesPerCategory: 0 });
|
|
1167
|
+
const examples = gen.generate();
|
|
1168
|
+
|
|
1169
|
+
expect(examples.length).toBe(0);
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
// ---------------------------------------------------------------------------
|
|
1174
|
+
// Integration: full pipeline
|
|
1175
|
+
// ---------------------------------------------------------------------------
|
|
1176
|
+
|
|
1177
|
+
describe('full pipeline integration', () => {
|
|
1178
|
+
it('should generate, export, and parse a complete JSONL dataset', () => {
|
|
1179
|
+
const gen = new SpatialTrainingDataGenerator({
|
|
1180
|
+
seed: 12345,
|
|
1181
|
+
examplesPerCategory: 5,
|
|
1182
|
+
relationshipTypes: ['spatial_adjacent', 'spatial_contains', 'spatial_reachable'],
|
|
1183
|
+
difficultyLevels: ['basic', 'intermediate', 'advanced'],
|
|
1184
|
+
positiveRatio: 0.5,
|
|
1185
|
+
includeContext: true,
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1188
|
+
// Generate
|
|
1189
|
+
const examples = gen.generate();
|
|
1190
|
+
expect(examples.length).toBe(45); // 3 types x 3 difficulties x 5 examples
|
|
1191
|
+
|
|
1192
|
+
// Export JSONL
|
|
1193
|
+
const jsonl = gen.exportJSONL(examples);
|
|
1194
|
+
const lines = jsonl.split('\n');
|
|
1195
|
+
expect(lines.length).toBe(45);
|
|
1196
|
+
|
|
1197
|
+
// Verify each line is valid
|
|
1198
|
+
for (const line of lines) {
|
|
1199
|
+
const entry = JSON.parse(line) as SpatialTrainingJSONLEntry;
|
|
1200
|
+
expect(entry.instruction.length).toBeGreaterThan(10);
|
|
1201
|
+
expect(entry.response.length).toBeGreaterThan(10);
|
|
1202
|
+
expect(['spatial_adjacent', 'spatial_contains', 'spatial_reachable']).toContain(
|
|
1203
|
+
entry.metadata.relationship_type
|
|
1204
|
+
);
|
|
1205
|
+
expect(['basic', 'intermediate', 'advanced']).toContain(entry.metadata.difficulty);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Get stats
|
|
1209
|
+
const stats = gen.getStats(examples);
|
|
1210
|
+
expect(stats.totalExamples).toBe(45);
|
|
1211
|
+
expect(stats.byRelationship.spatial_adjacent).toBe(15);
|
|
1212
|
+
expect(stats.byRelationship.spatial_contains).toBe(15);
|
|
1213
|
+
expect(stats.byRelationship.spatial_reachable).toBe(15);
|
|
1214
|
+
expect(stats.byDifficulty.basic).toBe(15);
|
|
1215
|
+
expect(stats.byDifficulty.intermediate).toBe(15);
|
|
1216
|
+
expect(stats.byDifficulty.advanced).toBe(15);
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
it('should produce training-quality instruction-response pairs', () => {
|
|
1220
|
+
const gen = createSeededGenerator({
|
|
1221
|
+
examplesPerCategory: 5,
|
|
1222
|
+
includeContext: true,
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
const examples = gen.generate();
|
|
1226
|
+
|
|
1227
|
+
for (const ex of examples) {
|
|
1228
|
+
// Instructions should be questions or directives
|
|
1229
|
+
expect(ex.instruction.length).toBeGreaterThan(10);
|
|
1230
|
+
// Responses should be informative answers
|
|
1231
|
+
expect(ex.response.length).toBeGreaterThan(10);
|
|
1232
|
+
// Context should be valid HoloScript
|
|
1233
|
+
expect(ex.context).toContain('composition');
|
|
1234
|
+
|
|
1235
|
+
// Responses for positive examples should indicate success/satisfaction
|
|
1236
|
+
if (ex.isPositive) {
|
|
1237
|
+
expect(ex.response.toLowerCase()).toMatch(
|
|
1238
|
+
/yes|satisf|pass|within|clear|contain|inside|proper|reach|no violation|maintain|unobstructed|unblocked|direct|measur/i
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
});
|