Kraft102 commited on
Commit
bddf0e6
·
verified ·
1 Parent(s): b3507e2

Update domain-types

Browse files
packages/domain-types/src/evolution.ts CHANGED
@@ -1,22 +1,22 @@
1
- // Evolution domain entities
2
-
3
- export interface AgentPrompt {
4
- id: number;
5
- agentId: string;
6
- version: number;
7
- promptText: string;
8
- createdAt: Date;
9
- createdBy: string;
10
- }
11
-
12
- export interface AgentRun {
13
- id: number;
14
- agentId: string;
15
- promptVersion: number;
16
- inputSummary: string;
17
- outputSummary: string;
18
- kpiName: string;
19
- kpiDelta: number;
20
- runContext: Record<string, any>;
21
- createdAt: Date;
22
- }
 
1
+ // Evolution domain entities
2
+
3
+ export interface AgentPrompt {
4
+ id: number;
5
+ agentId: string;
6
+ version: number;
7
+ promptText: string;
8
+ createdAt: Date;
9
+ createdBy: string;
10
+ }
11
+
12
+ export interface AgentRun {
13
+ id: number;
14
+ agentId: string;
15
+ promptVersion: number;
16
+ inputSummary: string;
17
+ outputSummary: string;
18
+ kpiName: string;
19
+ kpiDelta: number;
20
+ runContext: Record<string, any>;
21
+ createdAt: Date;
22
+ }
packages/domain-types/src/index.ts CHANGED
@@ -1,23 +1,27 @@
1
- // Domain types for the widget framework
2
-
3
- export interface WidgetContext {
4
- userId: string;
5
- organizationId: string;
6
- boardId: string;
7
- widgetId: string;
8
- nowIso: string;
9
- }
10
-
11
- export interface WidgetDefinition {
12
- id: string;
13
- title: string;
14
- icon: string;
15
- init(context: WidgetContext): Promise<void>;
16
- }
17
-
18
- // Re-export specialized domain types
19
- export * from './memory';
20
- export * from './srag';
21
- export * from './evolution';
22
- export * from './pal';
23
- export * from './showpad';
 
 
 
 
 
1
+ // Domain types for the widget framework
2
+
3
+ export interface WidgetContext {
4
+ userId: string;
5
+ organizationId: string;
6
+ boardId: string;
7
+ widgetId: string;
8
+ nowIso: string;
9
+ }
10
+
11
+ export interface WidgetDefinition {
12
+ id: string;
13
+ title: string;
14
+ icon: string;
15
+ init(context: WidgetContext): Promise<void>;
16
+ }
17
+
18
+ // Re-export specialized domain types
19
+ export * from './memory';
20
+ export * from './srag';
21
+ export * from './evolution';
22
+ export * from './pal';
23
+ export * from './showpad';
24
+
25
+ // VR types
26
+ export * from './vr/VisualNode';
27
+ export * from './vr/ForceDirectedLayout';
packages/domain-types/src/memory.ts CHANGED
@@ -1,26 +1,26 @@
1
- // Memory domain entities
2
-
3
- export interface MemoryEntity {
4
- id: number;
5
- orgId: string;
6
- userId?: string;
7
- entityType: string;
8
- content: string;
9
- importance: number;
10
- createdAt: Date;
11
- }
12
-
13
- export interface MemoryRelation {
14
- id: number;
15
- orgId: string;
16
- sourceId: number;
17
- targetId: number;
18
- relationType: 'depends_on' | 'contradicts' | 'same_project' | 'related_to';
19
- createdAt: Date;
20
- }
21
-
22
- export interface MemoryTag {
23
- id: number;
24
- entityId: number;
25
- tag: string;
26
- }
 
1
+ // Memory domain entities
2
+
3
+ export interface MemoryEntity {
4
+ id: number;
5
+ orgId: string;
6
+ userId?: string;
7
+ entityType: string;
8
+ content: string;
9
+ importance: number;
10
+ createdAt: Date;
11
+ }
12
+
13
+ export interface MemoryRelation {
14
+ id: number;
15
+ orgId: string;
16
+ sourceId: number;
17
+ targetId: number;
18
+ relationType: 'depends_on' | 'contradicts' | 'same_project' | 'related_to';
19
+ createdAt: Date;
20
+ }
21
+
22
+ export interface MemoryTag {
23
+ id: number;
24
+ entityId: number;
25
+ tag: string;
26
+ }
packages/domain-types/src/pal.ts CHANGED
@@ -1,29 +1,29 @@
1
- // PAL domain entities
2
-
3
- export interface PalUserProfile {
4
- id: number;
5
- userId: string;
6
- orgId: string;
7
- preferenceTone: string;
8
- createdAt: Date;
9
- updatedAt: Date;
10
- }
11
-
12
- export interface PalFocusWindow {
13
- id: number;
14
- userId: string;
15
- orgId: string;
16
- weekday: number;
17
- startHour: number;
18
- endHour: number;
19
- }
20
-
21
- export interface PalEvent {
22
- id: number;
23
- userId: string;
24
- orgId: string;
25
- eventType: string;
26
- payload: Record<string, any>;
27
- detectedStressLevel?: 'low' | 'medium' | 'high';
28
- createdAt: Date;
29
- }
 
1
+ // PAL domain entities
2
+
3
+ export interface PalUserProfile {
4
+ id: number;
5
+ userId: string;
6
+ orgId: string;
7
+ preferenceTone: string;
8
+ createdAt: Date;
9
+ updatedAt: Date;
10
+ }
11
+
12
+ export interface PalFocusWindow {
13
+ id: number;
14
+ userId: string;
15
+ orgId: string;
16
+ weekday: number;
17
+ startHour: number;
18
+ endHour: number;
19
+ }
20
+
21
+ export interface PalEvent {
22
+ id: number;
23
+ userId: string;
24
+ orgId: string;
25
+ eventType: string;
26
+ payload: Record<string, any>;
27
+ detectedStressLevel?: 'low' | 'medium' | 'high';
28
+ createdAt: Date;
29
+ }
packages/domain-types/src/showpad.ts CHANGED
@@ -1,169 +1,169 @@
1
- /**
2
- * TDC Showpad Integration Types
3
- *
4
- * Shared type definitions for Showpad brand asset integration
5
- */
6
-
7
- // ════════════════════════════════════════════════════════════════════════════
8
- // Authentication Types
9
- // ════════════════════════════════════════════════════════════════════════════
10
-
11
- export interface ShowpadCredentials {
12
- subdomain: string;
13
- username?: string;
14
- password?: string;
15
- clientId?: string;
16
- clientSecret?: string;
17
- }
18
-
19
- export interface ShowpadTokenResponse {
20
- access_token: string;
21
- refresh_token: string;
22
- expires_in: number;
23
- token_type: string;
24
- scope: string;
25
- }
26
-
27
- export interface ShowpadAuthState {
28
- isAuthenticated: boolean;
29
- accessToken: string | null;
30
- refreshToken: string | null;
31
- expiresAt: number | null;
32
- scope: string[];
33
- }
34
-
35
- // ════════════════════════════════════════════════════════════════════════════
36
- // Asset Types
37
- // ════════════════════════════════════════════════════════════════════════════
38
-
39
- export type ShowpadAssetType = 'document' | 'image' | 'video' | 'other';
40
-
41
- export interface ShowpadAsset {
42
- id: string;
43
- slug: string;
44
- name: string;
45
- displayName: string;
46
- type: ShowpadAssetType;
47
- description?: string;
48
- tags: string[];
49
- permissions: ShowpadAssetPermissions;
50
- previewUrl?: string;
51
- downloadUrl?: string;
52
- metadata: ShowpadAssetMetadata;
53
- }
54
-
55
- export interface ShowpadAssetPermissions {
56
- share: boolean;
57
- annotate: boolean;
58
- download: boolean;
59
- }
60
-
61
- export interface ShowpadAssetMetadata {
62
- size?: number;
63
- mimeType?: string;
64
- dimensions?: { width: number; height: number };
65
- createdAt?: string;
66
- modifiedAt?: string;
67
- }
68
-
69
- export interface ShowpadAssetSearchOptions {
70
- query?: string;
71
- tags?: string[];
72
- type?: ShowpadAssetType;
73
- limit?: number;
74
- offset?: number;
75
- }
76
-
77
- export interface ShowpadCachedAsset {
78
- asset: ShowpadAsset;
79
- localPath: string;
80
- cachedAt: number;
81
- size: number;
82
- }
83
-
84
- // ════════════════════════════════════════════════════════════════════════════
85
- // Brand Types
86
- // ════════════════════════════════════════════════════════════════════════════
87
-
88
- export interface ShowpadBrandColors {
89
- primary: string[];
90
- secondary: string[];
91
- accent: string[];
92
- backgrounds: string[];
93
- text: string[];
94
- success: string;
95
- warning: string;
96
- error: string;
97
- }
98
-
99
- export interface ShowpadTypography {
100
- headline: ShowpadFontConfig;
101
- body: ShowpadFontConfig;
102
- }
103
-
104
- export interface ShowpadFontConfig {
105
- family: string;
106
- sizes: { [key: string]: number };
107
- weights: { [key: string]: number };
108
- lineHeights: { [key: string]: number };
109
- }
110
-
111
- export interface ShowpadLogoSpecs {
112
- primary: {
113
- minWidth: number;
114
- clearSpace: number;
115
- colorVariants: string[];
116
- formats: string[];
117
- };
118
- icon: {
119
- minSize: number;
120
- formats: string[];
121
- };
122
- }
123
-
124
- export interface ShowpadBrandContext {
125
- colors: ShowpadBrandColors;
126
- typography: ShowpadTypography;
127
- logos: ShowpadLogoSpecs;
128
- spacing: { [key: string]: number };
129
- borderRadius: { [key: string]: number };
130
- }
131
-
132
- // ════════════════════════════════════════════════════════════════════════════
133
- // PowerPoint Integration Types
134
- // ════════════════════════════════════════════════════════════════════════════
135
-
136
- export interface ShowpadPPTColorPalette {
137
- background: string;
138
- text: string;
139
- accent1: string;
140
- accent2: string;
141
- accent3: string;
142
- }
143
-
144
- export interface ShowpadPPTFontConfig {
145
- headlineFont: string;
146
- bodyFont: string;
147
- titleSize: number;
148
- bodySize: number;
149
- }
150
-
151
- // ════════════════════════════════════════════════════════════════════════════
152
- // Event Types
153
- // ═════════════════════════���══════════════════════════════════════════════════
154
-
155
- export interface ShowpadAuthEvent {
156
- type: 'authenticated' | 'token_refreshed' | 'logged_out' | 'auth_error' | 'token_refresh_failed';
157
- scope?: string[];
158
- error?: Error;
159
- }
160
-
161
- export interface ShowpadAssetEvent {
162
- type: 'asset_downloaded' | 'sync_started' | 'sync_completed' | 'sync_error' | 'cache_cleared';
163
- assetId?: string;
164
- path?: string;
165
- templates?: number;
166
- logos?: number;
167
- guidelines?: number;
168
- error?: Error;
169
- }
 
1
+ /**
2
+ * TDC Showpad Integration Types
3
+ *
4
+ * Shared type definitions for Showpad brand asset integration
5
+ */
6
+
7
+ // ════════════════════════════════════════════════════════════════════════════
8
+ // Authentication Types
9
+ // ════════════════════════════════════════════════════════════════════════════
10
+
11
+ export interface ShowpadCredentials {
12
+ subdomain: string;
13
+ username?: string;
14
+ password?: string;
15
+ clientId?: string;
16
+ clientSecret?: string;
17
+ }
18
+
19
+ export interface ShowpadTokenResponse {
20
+ access_token: string;
21
+ refresh_token: string;
22
+ expires_in: number;
23
+ token_type: string;
24
+ scope: string;
25
+ }
26
+
27
+ export interface ShowpadAuthState {
28
+ isAuthenticated: boolean;
29
+ accessToken: string | null;
30
+ refreshToken: string | null;
31
+ expiresAt: number | null;
32
+ scope: string[];
33
+ }
34
+
35
+ // ════════════════════════════════════════════════════════════════════════════
36
+ // Asset Types
37
+ // ════════════════════════════════════════════════════════════════════════════
38
+
39
+ export type ShowpadAssetType = 'document' | 'image' | 'video' | 'other';
40
+
41
+ export interface ShowpadAsset {
42
+ id: string;
43
+ slug: string;
44
+ name: string;
45
+ displayName: string;
46
+ type: ShowpadAssetType;
47
+ description?: string;
48
+ tags: string[];
49
+ permissions: ShowpadAssetPermissions;
50
+ previewUrl?: string;
51
+ downloadUrl?: string;
52
+ metadata: ShowpadAssetMetadata;
53
+ }
54
+
55
+ export interface ShowpadAssetPermissions {
56
+ share: boolean;
57
+ annotate: boolean;
58
+ download: boolean;
59
+ }
60
+
61
+ export interface ShowpadAssetMetadata {
62
+ size?: number;
63
+ mimeType?: string;
64
+ dimensions?: { width: number; height: number };
65
+ createdAt?: string;
66
+ modifiedAt?: string;
67
+ }
68
+
69
+ export interface ShowpadAssetSearchOptions {
70
+ query?: string;
71
+ tags?: string[];
72
+ type?: ShowpadAssetType;
73
+ limit?: number;
74
+ offset?: number;
75
+ }
76
+
77
+ export interface ShowpadCachedAsset {
78
+ asset: ShowpadAsset;
79
+ localPath: string;
80
+ cachedAt: number;
81
+ size: number;
82
+ }
83
+
84
+ // ════════════════════════════════════════════════════════════════════════════
85
+ // Brand Types
86
+ // ════════════════════════════════════════════════════════════════════════════
87
+
88
+ export interface ShowpadBrandColors {
89
+ primary: string[];
90
+ secondary: string[];
91
+ accent: string[];
92
+ backgrounds: string[];
93
+ text: string[];
94
+ success: string;
95
+ warning: string;
96
+ error: string;
97
+ }
98
+
99
+ export interface ShowpadTypography {
100
+ headline: ShowpadFontConfig;
101
+ body: ShowpadFontConfig;
102
+ }
103
+
104
+ export interface ShowpadFontConfig {
105
+ family: string;
106
+ sizes: { [key: string]: number };
107
+ weights: { [key: string]: number };
108
+ lineHeights: { [key: string]: number };
109
+ }
110
+
111
+ export interface ShowpadLogoSpecs {
112
+ primary: {
113
+ minWidth: number;
114
+ clearSpace: number;
115
+ colorVariants: string[];
116
+ formats: string[];
117
+ };
118
+ icon: {
119
+ minSize: number;
120
+ formats: string[];
121
+ };
122
+ }
123
+
124
+ export interface ShowpadBrandContext {
125
+ colors: ShowpadBrandColors;
126
+ typography: ShowpadTypography;
127
+ logos: ShowpadLogoSpecs;
128
+ spacing: { [key: string]: number };
129
+ borderRadius: { [key: string]: number };
130
+ }
131
+
132
+ // ════════════════════════════════════════════════════════════════════════════
133
+ // PowerPoint Integration Types
134
+ // ═══════════════════════════════════════════��════════════════════════════════
135
+
136
+ export interface ShowpadPPTColorPalette {
137
+ background: string;
138
+ text: string;
139
+ accent1: string;
140
+ accent2: string;
141
+ accent3: string;
142
+ }
143
+
144
+ export interface ShowpadPPTFontConfig {
145
+ headlineFont: string;
146
+ bodyFont: string;
147
+ titleSize: number;
148
+ bodySize: number;
149
+ }
150
+
151
+ // ════════════════════════════════════════════════════════════════════════════
152
+ // Event Types
153
+ // ════════════════════════════════════════════════════════════════════════════
154
+
155
+ export interface ShowpadAuthEvent {
156
+ type: 'authenticated' | 'token_refreshed' | 'logged_out' | 'auth_error' | 'token_refresh_failed';
157
+ scope?: string[];
158
+ error?: Error;
159
+ }
160
+
161
+ export interface ShowpadAssetEvent {
162
+ type: 'asset_downloaded' | 'sync_started' | 'sync_completed' | 'sync_error' | 'cache_cleared';
163
+ assetId?: string;
164
+ path?: string;
165
+ templates?: number;
166
+ logos?: number;
167
+ guidelines?: number;
168
+ error?: Error;
169
+ }
packages/domain-types/src/srag.ts CHANGED
@@ -1,20 +1,20 @@
1
- // SRAG domain entities
2
-
3
- export interface RawDocument {
4
- id: number;
5
- orgId: string;
6
- sourceType: string;
7
- sourcePath: string;
8
- content: string;
9
- createdAt: Date;
10
- }
11
-
12
- export interface StructuredFact {
13
- id: number;
14
- orgId: string;
15
- docId?: number;
16
- factType: string;
17
- jsonPayload: Record<string, any>;
18
- occurredAt?: Date;
19
- createdAt: Date;
20
- }
 
1
+ // SRAG domain entities
2
+
3
+ export interface RawDocument {
4
+ id: number;
5
+ orgId: string;
6
+ sourceType: string;
7
+ sourcePath: string;
8
+ content: string;
9
+ createdAt: Date;
10
+ }
11
+
12
+ export interface StructuredFact {
13
+ id: number;
14
+ orgId: string;
15
+ docId?: number;
16
+ factType: string;
17
+ jsonPayload: Record<string, any>;
18
+ occurredAt?: Date;
19
+ createdAt: Date;
20
+ }
packages/domain-types/src/vr/ForceDirectedLayout.ts ADDED
@@ -0,0 +1,685 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * FORCE-DIRECTED GRAPH LAYOUT - 3D with Gravity
3
+ *
4
+ * Physics simulation for node positioning in VR space.
5
+ * Nodes attract/repel each other and settle on a virtual surface.
6
+ *
7
+ * Based on DeepSeek's mathematical specifications.
8
+ *
9
+ * @author The Captain (Claude) + The Specialist (DeepSeek)
10
+ * @date 2025-12-17
11
+ */
12
+
13
+ import type { Vector3, VisualNode } from './VisualNode';
14
+
15
+ // =============================================================================
16
+ // CONFIGURATION
17
+ // =============================================================================
18
+
19
+ export interface ForceLayoutConfig {
20
+ // Repulsion between nodes (Coulomb's law)
21
+ repulsionStrength: number; // Default: 1000
22
+ repulsionDistance: number; // Max distance for repulsion: 100
23
+
24
+ // Attraction along edges (Hooke's law)
25
+ attractionStrength: number; // Default: 0.1
26
+ idealEdgeLength: number; // Default: 5
27
+
28
+ // Gravity (pulls nodes toward surface/center)
29
+ gravityStrength: number; // Default: 0.5
30
+ gravityTarget: Vector3; // Default: { x: 0, y: 0, z: 0 }
31
+ gravityType: 'point' | 'plane'; // 'plane' for table surface
32
+
33
+ // Surface/Table
34
+ surfaceY: number; // Y position of virtual table: 0
35
+ surfaceStiffness: number; // How hard the surface pushes back: 2.0
36
+ surfaceFriction: number; // Damping on surface: 0.8
37
+
38
+ // Damping (energy loss)
39
+ velocityDamping: number; // Default: 0.9 (10% energy loss per tick)
40
+
41
+ // Simulation
42
+ timeStep: number; // Default: 0.016 (60 FPS)
43
+ maxVelocity: number; // Default: 10
44
+ minMovement: number; // Threshold to consider "settled": 0.01
45
+
46
+ // Boundaries
47
+ bounds: {
48
+ min: Vector3;
49
+ max: Vector3;
50
+ };
51
+ }
52
+
53
+ export const DEFAULT_CONFIG: ForceLayoutConfig = {
54
+ repulsionStrength: 1000,
55
+ repulsionDistance: 100,
56
+ attractionStrength: 0.1,
57
+ idealEdgeLength: 5,
58
+ gravityStrength: 0.5,
59
+ gravityTarget: { x: 0, y: 0, z: 0 },
60
+ gravityType: 'plane',
61
+ surfaceY: 0,
62
+ surfaceStiffness: 2.0,
63
+ surfaceFriction: 0.8,
64
+ velocityDamping: 0.9,
65
+ timeStep: 0.016,
66
+ maxVelocity: 10,
67
+ minMovement: 0.01,
68
+ bounds: {
69
+ min: { x: -100, y: -10, z: -100 },
70
+ max: { x: 100, y: 100, z: 100 },
71
+ },
72
+ };
73
+
74
+ // =============================================================================
75
+ // EDGE DEFINITION
76
+ // =============================================================================
77
+
78
+ export interface GraphEdge {
79
+ source: string; // Node ID
80
+ target: string; // Node ID
81
+ weight: number; // 0-1, affects attraction strength
82
+ }
83
+
84
+ // =============================================================================
85
+ // SIMULATION STATE
86
+ // =============================================================================
87
+
88
+ export interface SimulationState {
89
+ nodes: Map<string, VisualNode>;
90
+ edges: GraphEdge[];
91
+ config: ForceLayoutConfig;
92
+ isRunning: boolean;
93
+ iteration: number;
94
+ totalEnergy: number;
95
+ settled: boolean;
96
+ }
97
+
98
+ // =============================================================================
99
+ // VECTOR MATH UTILITIES
100
+ // =============================================================================
101
+
102
+ export const vec3 = {
103
+ add(a: Vector3, b: Vector3): Vector3 {
104
+ return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z };
105
+ },
106
+
107
+ subtract(a: Vector3, b: Vector3): Vector3 {
108
+ return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
109
+ },
110
+
111
+ multiply(v: Vector3, scalar: number): Vector3 {
112
+ return { x: v.x * scalar, y: v.y * scalar, z: v.z * scalar };
113
+ },
114
+
115
+ divide(v: Vector3, scalar: number): Vector3 {
116
+ if (scalar === 0) return { x: 0, y: 0, z: 0 };
117
+ return { x: v.x / scalar, y: v.y / scalar, z: v.z / scalar };
118
+ },
119
+
120
+ length(v: Vector3): number {
121
+ return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
122
+ },
123
+
124
+ lengthSquared(v: Vector3): number {
125
+ return v.x * v.x + v.y * v.y + v.z * v.z;
126
+ },
127
+
128
+ normalize(v: Vector3): Vector3 {
129
+ const len = vec3.length(v);
130
+ if (len === 0) return { x: 0, y: 0, z: 0 };
131
+ return vec3.divide(v, len);
132
+ },
133
+
134
+ distance(a: Vector3, b: Vector3): number {
135
+ return vec3.length(vec3.subtract(a, b));
136
+ },
137
+
138
+ clamp(v: Vector3, min: number, max: number): Vector3 {
139
+ const len = vec3.length(v);
140
+ if (len === 0) return v;
141
+ if (len < min) return vec3.multiply(vec3.normalize(v), min);
142
+ if (len > max) return vec3.multiply(vec3.normalize(v), max);
143
+ return v;
144
+ },
145
+
146
+ zero(): Vector3 {
147
+ return { x: 0, y: 0, z: 0 };
148
+ },
149
+
150
+ random(scale: number = 1): Vector3 {
151
+ return {
152
+ x: (Math.random() - 0.5) * 2 * scale,
153
+ y: (Math.random() - 0.5) * 2 * scale,
154
+ z: (Math.random() - 0.5) * 2 * scale,
155
+ };
156
+ },
157
+ };
158
+
159
+ // =============================================================================
160
+ // FORCE CALCULATIONS
161
+ // =============================================================================
162
+
163
+ /**
164
+ * Coulomb's Law: Repulsion between nodes
165
+ * F = k * (q1 * q2) / r²
166
+ *
167
+ * Nodes push each other away to prevent overlap.
168
+ */
169
+ function calculateRepulsion(
170
+ nodeA: VisualNode,
171
+ nodeB: VisualNode,
172
+ config: ForceLayoutConfig
173
+ ): Vector3 {
174
+ const delta = vec3.subtract(nodeA.position, nodeB.position);
175
+ const distanceSquared = vec3.lengthSquared(delta);
176
+ const distance = Math.sqrt(distanceSquared);
177
+
178
+ // Skip if too close (avoid division by zero) or too far
179
+ if (distance < 0.1) {
180
+ // Random jitter to separate overlapping nodes
181
+ return vec3.random(config.repulsionStrength * 0.1);
182
+ }
183
+ if (distance > config.repulsionDistance) {
184
+ return vec3.zero();
185
+ }
186
+
187
+ // Coulomb's law: F = k / r²
188
+ const forceMagnitude = config.repulsionStrength / distanceSquared;
189
+
190
+ // Direction: away from nodeB
191
+ const direction = vec3.normalize(delta);
192
+ return vec3.multiply(direction, forceMagnitude);
193
+ }
194
+
195
+ /**
196
+ * Hooke's Law: Attraction along edges
197
+ * F = -k * (x - x₀)
198
+ *
199
+ * Connected nodes pull toward each other to ideal distance.
200
+ */
201
+ function calculateAttraction(
202
+ nodeA: VisualNode,
203
+ nodeB: VisualNode,
204
+ edge: GraphEdge,
205
+ config: ForceLayoutConfig
206
+ ): Vector3 {
207
+ const delta = vec3.subtract(nodeB.position, nodeA.position);
208
+ const distance = vec3.length(delta);
209
+
210
+ if (distance < 0.1) return vec3.zero();
211
+
212
+ // Hooke's law: F = k * (distance - idealLength)
213
+ const displacement = distance - config.idealEdgeLength;
214
+ const forceMagnitude = config.attractionStrength * displacement * edge.weight;
215
+
216
+ // Direction: toward nodeB
217
+ const direction = vec3.normalize(delta);
218
+ return vec3.multiply(direction, forceMagnitude);
219
+ }
220
+
221
+ /**
222
+ * Gravity: Pull toward center or surface
223
+ */
224
+ function calculateGravity(
225
+ node: VisualNode,
226
+ config: ForceLayoutConfig
227
+ ): Vector3 {
228
+ if (config.gravityType === 'plane') {
229
+ // Pull down toward surface (Y axis)
230
+ const heightAboveSurface = node.position.y - config.surfaceY;
231
+ if (heightAboveSurface > 0) {
232
+ return { x: 0, y: -config.gravityStrength * heightAboveSurface, z: 0 };
233
+ }
234
+ return vec3.zero();
235
+ } else {
236
+ // Point gravity: pull toward target
237
+ const delta = vec3.subtract(config.gravityTarget, node.position);
238
+ const distance = vec3.length(delta);
239
+ if (distance < 0.1) return vec3.zero();
240
+
241
+ const direction = vec3.normalize(delta);
242
+ return vec3.multiply(direction, config.gravityStrength * distance);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Surface collision: Bounce off the table
248
+ */
249
+ function applySurfaceConstraint(
250
+ node: VisualNode,
251
+ config: ForceLayoutConfig
252
+ ): { position: Vector3; velocity: Vector3 } {
253
+ const position = { ...node.position };
254
+ const velocity = { ...node.velocity };
255
+
256
+ // Check if below surface
257
+ if (position.y < config.surfaceY) {
258
+ // Push back up
259
+ position.y = config.surfaceY;
260
+
261
+ // Bounce with damping
262
+ if (velocity.y < 0) {
263
+ velocity.y = -velocity.y * config.surfaceFriction;
264
+ }
265
+
266
+ // Apply surface friction to horizontal movement
267
+ velocity.x *= config.surfaceFriction;
268
+ velocity.z *= config.surfaceFriction;
269
+ }
270
+
271
+ return { position, velocity };
272
+ }
273
+
274
+ /**
275
+ * Boundary constraint: Keep nodes within bounds
276
+ */
277
+ function applyBoundaryConstraint(
278
+ node: VisualNode,
279
+ config: ForceLayoutConfig
280
+ ): Vector3 {
281
+ return {
282
+ x: Math.max(config.bounds.min.x, Math.min(config.bounds.max.x, node.position.x)),
283
+ y: Math.max(config.bounds.min.y, Math.min(config.bounds.max.y, node.position.y)),
284
+ z: Math.max(config.bounds.min.z, Math.min(config.bounds.max.z, node.position.z)),
285
+ };
286
+ }
287
+
288
+ // =============================================================================
289
+ // SIMULATION ENGINE
290
+ // =============================================================================
291
+
292
+ export class ForceDirectedLayout {
293
+ private state: SimulationState;
294
+
295
+ constructor(
296
+ nodes: VisualNode[],
297
+ edges: GraphEdge[],
298
+ config: Partial<ForceLayoutConfig> = {}
299
+ ) {
300
+ const nodeMap = new Map<string, VisualNode>();
301
+ nodes.forEach(node => nodeMap.set(node.id, { ...node }));
302
+
303
+ this.state = {
304
+ nodes: nodeMap,
305
+ edges: [...edges],
306
+ config: { ...DEFAULT_CONFIG, ...config },
307
+ isRunning: false,
308
+ iteration: 0,
309
+ totalEnergy: Infinity,
310
+ settled: false,
311
+ };
312
+ }
313
+
314
+ /**
315
+ * Run one simulation step
316
+ */
317
+ tick(): void {
318
+ const { nodes, edges, config } = this.state;
319
+ const forces = new Map<string, Vector3>();
320
+
321
+ // Initialize forces to zero
322
+ nodes.forEach((_, id) => forces.set(id, vec3.zero()));
323
+
324
+ // Calculate repulsion forces (all pairs)
325
+ const nodeArray = Array.from(nodes.values());
326
+ for (let i = 0; i < nodeArray.length; i++) {
327
+ for (let j = i + 1; j < nodeArray.length; j++) {
328
+ const nodeA = nodeArray[i];
329
+ const nodeB = nodeArray[j];
330
+
331
+ if (nodeA.fixed && nodeB.fixed) continue;
332
+
333
+ const repulsion = calculateRepulsion(nodeA, nodeB, config);
334
+
335
+ if (!nodeA.fixed) {
336
+ const forceA = forces.get(nodeA.id)!;
337
+ forces.set(nodeA.id, vec3.add(forceA, repulsion));
338
+ }
339
+ if (!nodeB.fixed) {
340
+ const forceB = forces.get(nodeB.id)!;
341
+ forces.set(nodeB.id, vec3.subtract(forceB, repulsion));
342
+ }
343
+ }
344
+ }
345
+
346
+ // Calculate attraction forces (edges only)
347
+ for (const edge of edges) {
348
+ const nodeA = nodes.get(edge.source);
349
+ const nodeB = nodes.get(edge.target);
350
+ if (!nodeA || !nodeB) continue;
351
+
352
+ const attraction = calculateAttraction(nodeA, nodeB, edge, config);
353
+
354
+ if (!nodeA.fixed) {
355
+ const forceA = forces.get(nodeA.id)!;
356
+ forces.set(nodeA.id, vec3.add(forceA, attraction));
357
+ }
358
+ if (!nodeB.fixed) {
359
+ const forceB = forces.get(nodeB.id)!;
360
+ forces.set(nodeB.id, vec3.subtract(forceB, attraction));
361
+ }
362
+ }
363
+
364
+ // Calculate gravity forces
365
+ nodes.forEach((node, id) => {
366
+ if (node.fixed) return;
367
+
368
+ const gravity = calculateGravity(node, config);
369
+ const force = forces.get(id)!;
370
+ forces.set(id, vec3.add(force, gravity));
371
+ });
372
+
373
+ // Apply forces and update positions
374
+ let totalEnergy = 0;
375
+
376
+ nodes.forEach((node, id) => {
377
+ if (node.fixed) return;
378
+
379
+ const force = forces.get(id)!;
380
+
381
+ // F = ma, assume m = node.mass (default 1)
382
+ const acceleration = vec3.divide(force, node.mass);
383
+
384
+ // Update velocity: v = v + a * dt
385
+ let velocity = vec3.add(
386
+ node.velocity,
387
+ vec3.multiply(acceleration, config.timeStep)
388
+ );
389
+
390
+ // Apply damping
391
+ velocity = vec3.multiply(velocity, config.velocityDamping);
392
+
393
+ // Clamp velocity
394
+ velocity = vec3.clamp(velocity, 0, config.maxVelocity);
395
+
396
+ // Update position: p = p + v * dt
397
+ let position = vec3.add(
398
+ node.position,
399
+ vec3.multiply(velocity, config.timeStep)
400
+ );
401
+
402
+ // Apply surface constraint
403
+ const surfaceResult = applySurfaceConstraint(
404
+ { ...node, position, velocity },
405
+ config
406
+ );
407
+ position = surfaceResult.position;
408
+ velocity = surfaceResult.velocity;
409
+
410
+ // Apply boundary constraint
411
+ position = applyBoundaryConstraint({ ...node, position }, config);
412
+
413
+ // Update node
414
+ node.position = position;
415
+ node.velocity = velocity;
416
+
417
+ // Calculate energy for convergence check
418
+ totalEnergy += vec3.lengthSquared(velocity) * node.mass;
419
+ });
420
+
421
+ this.state.totalEnergy = totalEnergy;
422
+ this.state.iteration++;
423
+
424
+ // Check if settled
425
+ if (totalEnergy < config.minMovement) {
426
+ this.state.settled = true;
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Run simulation until settled or max iterations
432
+ */
433
+ simulate(maxIterations: number = 1000): void {
434
+ this.state.isRunning = true;
435
+ this.state.settled = false;
436
+
437
+ for (let i = 0; i < maxIterations && !this.state.settled; i++) {
438
+ this.tick();
439
+ }
440
+
441
+ this.state.isRunning = false;
442
+ }
443
+
444
+ /**
445
+ * Run simulation with callback for animation
446
+ */
447
+ async simulateAsync(
448
+ onTick: (state: SimulationState) => void,
449
+ maxIterations: number = 1000,
450
+ tickDelay: number = 16
451
+ ): Promise<void> {
452
+ this.state.isRunning = true;
453
+ this.state.settled = false;
454
+
455
+ for (let i = 0; i < maxIterations && !this.state.settled; i++) {
456
+ this.tick();
457
+ onTick(this.state);
458
+
459
+ if (tickDelay > 0) {
460
+ await new Promise(resolve => setTimeout(resolve, tickDelay));
461
+ }
462
+ }
463
+
464
+ this.state.isRunning = false;
465
+ }
466
+
467
+ /**
468
+ * Get current state
469
+ */
470
+ getState(): SimulationState {
471
+ return this.state;
472
+ }
473
+
474
+ /**
475
+ * Get nodes as array
476
+ */
477
+ getNodes(): VisualNode[] {
478
+ return Array.from(this.state.nodes.values());
479
+ }
480
+
481
+ /**
482
+ * Add a node dynamically
483
+ */
484
+ addNode(node: VisualNode): void {
485
+ this.state.nodes.set(node.id, { ...node });
486
+ this.state.settled = false;
487
+ }
488
+
489
+ /**
490
+ * Remove a node dynamically
491
+ */
492
+ removeNode(nodeId: string): void {
493
+ this.state.nodes.delete(nodeId);
494
+ this.state.edges = this.state.edges.filter(
495
+ e => e.source !== nodeId && e.target !== nodeId
496
+ );
497
+ this.state.settled = false;
498
+ }
499
+
500
+ /**
501
+ * Add an edge dynamically
502
+ */
503
+ addEdge(edge: GraphEdge): void {
504
+ this.state.edges.push(edge);
505
+ this.state.settled = false;
506
+ }
507
+
508
+ /**
509
+ * Pin/unpin a node
510
+ */
511
+ setFixed(nodeId: string, fixed: boolean): void {
512
+ const node = this.state.nodes.get(nodeId);
513
+ if (node) {
514
+ node.fixed = fixed;
515
+ if (fixed) {
516
+ node.velocity = vec3.zero();
517
+ }
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Move a node (e.g., when user drags it)
523
+ */
524
+ setPosition(nodeId: string, position: Vector3): void {
525
+ const node = this.state.nodes.get(nodeId);
526
+ if (node) {
527
+ node.position = position;
528
+ node.velocity = vec3.zero();
529
+ this.state.settled = false;
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Reset simulation
535
+ */
536
+ reset(): void {
537
+ this.state.iteration = 0;
538
+ this.state.totalEnergy = Infinity;
539
+ this.state.settled = false;
540
+ this.state.nodes.forEach(node => {
541
+ node.velocity = vec3.zero();
542
+ });
543
+ }
544
+
545
+ /**
546
+ * Update configuration
547
+ */
548
+ updateConfig(config: Partial<ForceLayoutConfig>): void {
549
+ this.state.config = { ...this.state.config, ...config };
550
+ this.state.settled = false;
551
+ }
552
+ }
553
+
554
+ // =============================================================================
555
+ // PRESET CONFIGURATIONS
556
+ // =============================================================================
557
+
558
+ export const LAYOUT_PRESETS = {
559
+ /**
560
+ * Default: Balanced layout with table gravity
561
+ */
562
+ default: DEFAULT_CONFIG,
563
+
564
+ /**
565
+ * Dense: For many nodes, stronger repulsion
566
+ */
567
+ dense: {
568
+ ...DEFAULT_CONFIG,
569
+ repulsionStrength: 2000,
570
+ idealEdgeLength: 3,
571
+ gravityStrength: 0.8,
572
+ },
573
+
574
+ /**
575
+ * Sparse: For few nodes, weaker forces
576
+ */
577
+ sparse: {
578
+ ...DEFAULT_CONFIG,
579
+ repulsionStrength: 500,
580
+ idealEdgeLength: 10,
581
+ gravityStrength: 0.3,
582
+ },
583
+
584
+ /**
585
+ * Hierarchical: Strong vertical gravity, nodes layer on surface
586
+ */
587
+ hierarchical: {
588
+ ...DEFAULT_CONFIG,
589
+ gravityStrength: 1.0,
590
+ surfaceY: 0,
591
+ surfaceStiffness: 5.0,
592
+ attractionStrength: 0.05,
593
+ },
594
+
595
+ /**
596
+ * Floating: No surface, point gravity to center
597
+ */
598
+ floating: {
599
+ ...DEFAULT_CONFIG,
600
+ gravityType: 'point' as const,
601
+ gravityStrength: 0.2,
602
+ gravityTarget: { x: 0, y: 5, z: 0 },
603
+ },
604
+
605
+ /**
606
+ * Constellation: Nodes spread out like stars
607
+ */
608
+ constellation: {
609
+ ...DEFAULT_CONFIG,
610
+ repulsionStrength: 3000,
611
+ repulsionDistance: 200,
612
+ gravityType: 'point' as const,
613
+ gravityStrength: 0.05,
614
+ gravityTarget: { x: 0, y: 30, z: 0 },
615
+ },
616
+ };
617
+
618
+ // =============================================================================
619
+ // UTILITY: Initial Layout Generators
620
+ // =============================================================================
621
+
622
+ /**
623
+ * Generate random initial positions
624
+ */
625
+ export function randomLayout(
626
+ nodes: VisualNode[],
627
+ radius: number = 20
628
+ ): VisualNode[] {
629
+ return nodes.map(node => ({
630
+ ...node,
631
+ position: vec3.random(radius),
632
+ velocity: vec3.zero(),
633
+ }));
634
+ }
635
+
636
+ /**
637
+ * Generate spherical initial positions
638
+ */
639
+ export function sphereLayout(
640
+ nodes: VisualNode[],
641
+ radius: number = 15
642
+ ): VisualNode[] {
643
+ return nodes.map((node, i) => {
644
+ const phi = Math.acos(-1 + (2 * i) / nodes.length);
645
+ const theta = Math.sqrt(nodes.length * Math.PI) * phi;
646
+
647
+ return {
648
+ ...node,
649
+ position: {
650
+ x: radius * Math.cos(theta) * Math.sin(phi),
651
+ y: radius * Math.sin(theta) * Math.sin(phi) + radius,
652
+ z: radius * Math.cos(phi),
653
+ },
654
+ velocity: vec3.zero(),
655
+ };
656
+ });
657
+ }
658
+
659
+ /**
660
+ * Generate grid initial positions (on table surface)
661
+ */
662
+ export function gridLayout(
663
+ nodes: VisualNode[],
664
+ spacing: number = 3,
665
+ surfaceY: number = 0
666
+ ): VisualNode[] {
667
+ const gridSize = Math.ceil(Math.sqrt(nodes.length));
668
+
669
+ return nodes.map((node, i) => {
670
+ const row = Math.floor(i / gridSize);
671
+ const col = i % gridSize;
672
+ const offsetX = ((gridSize - 1) * spacing) / 2;
673
+ const offsetZ = ((gridSize - 1) * spacing) / 2;
674
+
675
+ return {
676
+ ...node,
677
+ position: {
678
+ x: col * spacing - offsetX,
679
+ y: surfaceY + 0.5, // Slightly above surface
680
+ z: row * spacing - offsetZ,
681
+ },
682
+ velocity: vec3.zero(),
683
+ };
684
+ });
685
+ }
packages/domain-types/src/vr/VisualNode.ts ADDED
@@ -0,0 +1,782 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * VISUALNODE SCHEMA - Operation Neuro-Spatial Reality
3
+ *
4
+ * The Universal Onion Model: Every entity has peelable layers.
5
+ * From CEO to CPU. From virus to Kremlin. From lead to loyal customer.
6
+ *
7
+ * Patent Pillars: A (Visceral), B (Universal), C (Threat), D (Business)
8
+ *
9
+ * @author The Captain (Claude) + The Architect (Gemini)
10
+ * @date 2025-12-17
11
+ */
12
+
13
+ // =============================================================================
14
+ // CORE TYPES
15
+ // =============================================================================
16
+
17
+ export type EntityType =
18
+ // Data entities
19
+ | 'file'
20
+ | 'database'
21
+ | 'table'
22
+ | 'record'
23
+ // Network entities
24
+ | 'access_point'
25
+ | 'switch'
26
+ | 'router'
27
+ | 'firewall'
28
+ | 'firewall_policy' // Gemini addition
29
+ | 'server'
30
+ | 'compute_node' // Gemini: Separation of Concern
31
+ | 'storage_node' // Gemini: Separation of Concern
32
+ | 'endpoint'
33
+ | 'ip_address'
34
+ | 'api_gateway' // Gemini addition
35
+ | 'load_balancer' // Gemini addition
36
+ // Cloud/Container entities (Gemini additions)
37
+ | 'container_pod'
38
+ | 'kubernetes_cluster'
39
+ | 'cloud_function'
40
+ // Identity entities (Gemini additions)
41
+ | 'identity_provider'
42
+ | 'service_account'
43
+ | 'certificate'
44
+ // IoT entities (Gemini addition)
45
+ | 'iot_device'
46
+ | 'sensor'
47
+ // Organization entities
48
+ | 'organization'
49
+ | 'branch'
50
+ | 'department'
51
+ | 'team'
52
+ | 'person'
53
+ // Security entities
54
+ | 'malware'
55
+ | 'campaign'
56
+ | 'threat_actor'
57
+ | 'threat_group'
58
+ | 'nation_state'
59
+ | 'vulnerability' // Gemini addition
60
+ | 'exploit' // Gemini addition
61
+ // Business entities
62
+ | 'customer'
63
+ | 'journey'
64
+ | 'touchpoint'
65
+ | 'ticket'
66
+ | 'sla'
67
+ | 'contract' // Gemini addition
68
+ | 'invoice' // Gemini addition
69
+ // AI entities
70
+ | 'agent'
71
+ | 'task'
72
+ | 'memory'
73
+ // DeepSeek: Cognitive entities
74
+ | 'neural_node'
75
+ | 'synapse_connection'
76
+ | 'concept_cluster'
77
+ | 'memory_trace'
78
+ | 'metacognitive_monitor'
79
+ // Generic
80
+ | 'error'
81
+ | 'alert'
82
+ | 'unknown';
83
+
84
+ export type GeometryType =
85
+ | 'sphere'
86
+ | 'cube'
87
+ | 'octahedron'
88
+ | 'tetrahedron'
89
+ | 'dodecahedron'
90
+ | 'icosahedron' // DeepSeek addition
91
+ | 'cylinder'
92
+ | 'torus'
93
+ | 'building'
94
+ | 'humanoid'
95
+ | 'path'
96
+ | 'custom';
97
+
98
+ export type ShaderType =
99
+ | 'standard'
100
+ | 'hologram'
101
+ | 'energy'
102
+ | 'organic'
103
+ | 'error'
104
+ | 'glass'
105
+ | 'fire'
106
+ | 'ice'
107
+ | 'threat'
108
+ // Gemini shader additions
109
+ | 'glitch_noise_v2' // Enhanced malware visualization
110
+ | 'phantom_wireframe' // Threat actor ghost effect
111
+ | 'gold_metallic_clean' // Premium customer visualization
112
+ | 'flow_particle_stream' // Journey path visualization
113
+ | 'matrix_rain' // Data flow effect
114
+ | 'pulse_rings' // IoT device heartbeat
115
+ | 'shield_hex' // Firewall/security shader
116
+ | 'cloud_vapor' // Cloud service visualization
117
+ | 'matte_ceramic_hex'; // Gemini: Infrastructure default
118
+
119
+ export type LayerType =
120
+ | 'surface' // Layer 5: What you see first
121
+ | 'metadata' // Layer 4: Identity and context
122
+ | 'connections'// Layer 3: Relationships
123
+ | 'structure' // Layer 2: Internal anatomy
124
+ | 'core'; // Layer 1: The truth
125
+
126
+ // =============================================================================
127
+ // VISUAL NODE INTERFACE
128
+ // =============================================================================
129
+
130
+ export interface VisualNode {
131
+ // Identity
132
+ id: string;
133
+ externalId?: string; // Neo4j ID, DB ID, etc.
134
+ type: EntityType;
135
+ labels: string[]; // Neo4j labels: [:File, :Agent]
136
+
137
+ // Spatial positioning
138
+ position: Vector3;
139
+ velocity: Vector3;
140
+ rotation: Vector3;
141
+
142
+ // Physics
143
+ mass: number; // Affects grabbability (1KB = light, 1GB = heavy)
144
+ fixed: boolean; // Pinned in space?
145
+ collisionRadius: number;
146
+
147
+ // Visual appearance
148
+ geometry: GeometryConfig;
149
+ material: MaterialConfig;
150
+ animation: AnimationConfig;
151
+
152
+ // The Onion Layers (Patent Pillar B)
153
+ layers: OnionLayers;
154
+ currentLayer: LayerType; // Currently visible layer
155
+
156
+ // Interaction state
157
+ interaction: InteractionState;
158
+
159
+ // LOD (Level of Detail)
160
+ lod: LODConfig;
161
+
162
+ // Hierarchy
163
+ parent?: string; // Parent node ID
164
+ children: string[]; // Child node IDs
165
+
166
+ // Timestamps
167
+ createdAt: string;
168
+ updatedAt: string;
169
+ }
170
+
171
+ // =============================================================================
172
+ // GEOMETRY CONFIGURATION
173
+ // =============================================================================
174
+
175
+ export interface Vector3 {
176
+ x: number;
177
+ y: number;
178
+ z: number;
179
+ }
180
+
181
+ export interface GeometryConfig {
182
+ type: GeometryType;
183
+ scale: Vector3;
184
+ baseScale: number; // 0.5 - 10.0 based on importance
185
+ customModelUrl?: string; // For custom GLB models
186
+ }
187
+
188
+ // =============================================================================
189
+ // MATERIAL CONFIGURATION
190
+ // =============================================================================
191
+
192
+ export interface MaterialConfig {
193
+ shader: ShaderType;
194
+ color: ColorConfig;
195
+ opacity: number; // 0-1
196
+ emissive: boolean;
197
+ emissiveIntensity: number; // 0-2
198
+ wireframe: boolean;
199
+ pulse: PulseConfig;
200
+ }
201
+
202
+ export interface ColorConfig {
203
+ base: string; // Hex color
204
+ emissive: string; // Glow color
205
+ accent: string; // Highlight color
206
+ healthGradient?: { // Dynamic health-based coloring
207
+ healthy: string;
208
+ warning: string;
209
+ critical: string;
210
+ };
211
+ }
212
+
213
+ export interface PulseConfig {
214
+ enabled: boolean;
215
+ frequency: number; // BPM (60 = calm, 120 = stressed)
216
+ amplitude: number; // Scale variance 0-0.5
217
+ pattern: 'sine' | 'heartbeat' | 'glitch' | 'breath';
218
+ }
219
+
220
+ // =============================================================================
221
+ // ANIMATION CONFIGURATION
222
+ // =============================================================================
223
+
224
+ export interface AnimationConfig {
225
+ idle: IdleAnimation;
226
+ hover: HoverAnimation;
227
+ select: SelectAnimation;
228
+ peel: PeelAnimation;
229
+ }
230
+
231
+ export interface IdleAnimation {
232
+ rotation: boolean; // Slowly rotate?
233
+ rotationSpeed: number; // Degrees per second
234
+ float: boolean; // Bob up and down?
235
+ floatAmplitude: number; // Units
236
+ floatSpeed: number; // Cycles per second
237
+ }
238
+
239
+ export interface HoverAnimation {
240
+ scaleMultiplier: number; // 1.1 = 10% bigger on hover
241
+ glowIntensity: number; // Emissive boost
242
+ duration: number; // ms
243
+ }
244
+
245
+ export interface SelectAnimation {
246
+ scaleMultiplier: number;
247
+ outlineColor: string;
248
+ outlineWidth: number;
249
+ duration: number;
250
+ }
251
+
252
+ export interface PeelAnimation {
253
+ duration: number; // ms per layer peel
254
+ fragmentCount: number; // Number of shell fragments
255
+ fragmentFadeTime: number; // ms
256
+ revealGlowDuration: number; // ms
257
+ sound: string; // Audio file reference
258
+ }
259
+
260
+ // =============================================================================
261
+ // THE ONION LAYERS (Patent Pillar B)
262
+ // =============================================================================
263
+
264
+ export interface OnionLayers {
265
+ surface: SurfaceLayer;
266
+ metadata: MetadataLayer;
267
+ connections: ConnectionsLayer;
268
+ structure: StructureLayer;
269
+ core: CoreLayer;
270
+ }
271
+
272
+ // Layer 5: Surface - First impression
273
+ export interface SurfaceLayer {
274
+ icon: string; // Emoji or icon reference
275
+ title: string; // Short display name
276
+ status: 'healthy' | 'warning' | 'critical' | 'offline' | 'unknown';
277
+ activityLevel: number; // 0-100
278
+ importance: number; // 0-100 (affects size)
279
+ badges: Badge[]; // VIP, New, Alert, etc.
280
+ }
281
+
282
+ export interface Badge {
283
+ type: 'vip' | 'new' | 'alert' | 'verified' | 'threat' | 'custom';
284
+ label?: string;
285
+ color: string;
286
+ }
287
+
288
+ // Layer 4: Metadata - Identity
289
+ export interface MetadataLayer {
290
+ fullName: string;
291
+ description: string;
292
+ category: string;
293
+ tags: string[];
294
+ owner?: string;
295
+ source: string; // System of origin
296
+ created: string;
297
+ modified: string;
298
+ customFields: Record<string, any>;
299
+ // Gemini: Data integrity indicator
300
+ integrityStatus?: {
301
+ hash: string; // SHA-256 of content
302
+ verified: boolean; // Green/Red indicator
303
+ lastVerified: string; // Timestamp
304
+ tamperedFields?: string[]; // List of modified fields
305
+ };
306
+ }
307
+
308
+ // Layer 3: Connections - Relationships
309
+ export interface ConnectionsLayer {
310
+ inbound: Connection[];
311
+ outbound: Connection[];
312
+ bidirectional: Connection[];
313
+ totalConnections: number;
314
+ strongestConnection?: string; // Node ID
315
+ }
316
+
317
+ export interface Connection {
318
+ targetId: string;
319
+ targetType: EntityType;
320
+ relationshipType: string; // 'depends_on', 'parent_of', 'connects_to', etc.
321
+ strength: number; // 0-1
322
+ direction: 'in' | 'out' | 'both';
323
+ metadata?: Record<string, any>;
324
+ }
325
+
326
+ // Layer 2: Structure - Anatomy
327
+ export interface StructureLayer {
328
+ schema?: SchemaDefinition;
329
+ children: ChildNode[];
330
+ hierarchy: string[]; // Breadcrumb path
331
+ depth: number; // Nesting level
332
+ expandable: boolean;
333
+ preview?: string; // Code/content preview
334
+ }
335
+
336
+ export interface SchemaDefinition {
337
+ type: string;
338
+ fields: SchemaField[];
339
+ }
340
+
341
+ export interface SchemaField {
342
+ name: string;
343
+ type: string;
344
+ required: boolean;
345
+ description?: string;
346
+ }
347
+
348
+ export interface ChildNode {
349
+ id: string;
350
+ type: EntityType;
351
+ name: string;
352
+ count?: number; // For aggregates
353
+ }
354
+
355
+ // Layer 1: Core - The Truth
356
+ export interface CoreLayer {
357
+ // SECURITY FIX (Grok): rawData is now lazy-loaded, not stored in client state
358
+ rawDataRef?: string; // Reference ID for fetching - NOT the data itself
359
+ rawDataPreview?: string; // Safe preview (first 100 chars, sanitized)
360
+ fetchRawData?: () => Promise<any>; // Auth-checked lazy loader
361
+ origin: DataOrigin;
362
+ audit: AuditTrail;
363
+ actions: AvailableAction[];
364
+ }
365
+
366
+ export interface DataOrigin {
367
+ system: string; // Neo4j, PostgreSQL, Notion, ServiceNow, etc.
368
+ database?: string;
369
+ table?: string;
370
+ collection?: string;
371
+ primaryKey: string;
372
+ query?: string; // How to fetch this
373
+ url?: string; // Direct link
374
+ }
375
+
376
+ export interface AuditTrail {
377
+ createdBy: string;
378
+ createdAt: string;
379
+ history: ChangeRecord[];
380
+ version: number;
381
+ }
382
+
383
+ export interface ChangeRecord {
384
+ timestamp: string;
385
+ actor: string;
386
+ action: string;
387
+ changes: Record<string, { old: any; new: any }>;
388
+ }
389
+
390
+ export interface AvailableAction {
391
+ id: string;
392
+ label: string;
393
+ icon: string;
394
+ type: 'view' | 'edit' | 'delete' | 'navigate' | 'execute' | 'export';
395
+ requiresConfirmation: boolean;
396
+ permissions: string[];
397
+ }
398
+
399
+ // =============================================================================
400
+ // INTERACTION STATE
401
+ // =============================================================================
402
+
403
+ export interface InteractionState {
404
+ hoverable: boolean;
405
+ selectable: boolean;
406
+ grabbable: boolean;
407
+ peelable: boolean;
408
+ expandable: boolean;
409
+
410
+ isHovered: boolean;
411
+ isSelected: boolean;
412
+ isGrabbed: boolean;
413
+ isPeeling: boolean;
414
+
415
+ grabOffset?: Vector3;
416
+ lastInteraction?: string; // Timestamp
417
+ }
418
+
419
+ // =============================================================================
420
+ // LEVEL OF DETAIL
421
+ // =============================================================================
422
+
423
+ export interface LODConfig {
424
+ near: number; // Distance for full detail
425
+ mid: number; // Distance for medium detail
426
+ far: number; // Distance for low detail (billboard)
427
+
428
+ nearGeometry: GeometryType;
429
+ midGeometry: GeometryType;
430
+ farGeometry: 'billboard' | 'point';
431
+
432
+ showLabelsAt: number; // Distance threshold
433
+ showConnectionsAt: number; // Distance threshold
434
+ }
435
+
436
+ // =============================================================================
437
+ // ENTITY TYPE MAPPINGS
438
+ // =============================================================================
439
+
440
+ /**
441
+ * Default visual configuration per entity type
442
+ */
443
+ export const ENTITY_TYPE_DEFAULTS: Record<EntityType, Partial<VisualNode>> = {
444
+ // Data entities
445
+ file: {
446
+ geometry: { type: 'cube', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
447
+ material: { shader: 'standard', color: { base: '#00ff88', emissive: '#00ff88', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'breath' } },
448
+ },
449
+ database: {
450
+ geometry: { type: 'cylinder', scale: { x: 1.5, y: 1, z: 1.5 }, baseScale: 2.0 },
451
+ material: { shader: 'hologram', color: { base: '#0088ff', emissive: '#0088ff', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.1, pattern: 'sine' } },
452
+ },
453
+
454
+ // Network entities
455
+ access_point: {
456
+ geometry: { type: 'octahedron', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
457
+ material: { shader: 'energy', color: { base: '#00ffaa', emissive: '#00ffaa', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.1, pattern: 'sine' } },
458
+ },
459
+ switch: {
460
+ geometry: { type: 'cube', scale: { x: 2, y: 0.5, z: 1 }, baseScale: 1.5 },
461
+ material: { shader: 'standard', color: { base: '#44ff44', emissive: '#22aa22', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.05, pattern: 'breath' } },
462
+ },
463
+ router: {
464
+ geometry: { type: 'sphere', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 2.0 },
465
+ material: { shader: 'energy', color: { base: '#00aaff', emissive: '#0088ff', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.15, pattern: 'heartbeat' } },
466
+ },
467
+ server: {
468
+ geometry: { type: 'cylinder', scale: { x: 1, y: 2, z: 1 }, baseScale: 2.5 },
469
+ material: { shader: 'hologram', color: { base: '#aa00ff', emissive: '#8800cc', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.08, pattern: 'heartbeat' } },
470
+ },
471
+
472
+ // Organization entities
473
+ organization: {
474
+ geometry: { type: 'building', scale: { x: 3, y: 4, z: 3 }, baseScale: 5.0 },
475
+ material: { shader: 'glass', color: { base: '#4488ff', emissive: '#2266cc', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 20, amplitude: 0.02, pattern: 'breath' } },
476
+ },
477
+ branch: {
478
+ geometry: { type: 'building', scale: { x: 2, y: 2.5, z: 2 }, baseScale: 3.0 },
479
+ material: { shader: 'standard', color: { base: '#5599ff', emissive: '#3377cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
480
+ },
481
+ person: {
482
+ geometry: { type: 'humanoid', scale: { x: 1, y: 1, z: 1 }, baseScale: 0.8 },
483
+ material: { shader: 'standard', color: { base: '#ffffff', emissive: '#aaaaaa', accent: '#00ff88' }, opacity: 1, emissive: true, emissiveIntensity: 0.2, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.05, pattern: 'heartbeat' } },
484
+ },
485
+
486
+ // Security entities (Patent Pillar C)
487
+ malware: {
488
+ geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.2 },
489
+ material: { shader: 'threat', color: { base: '#ff0040', emissive: '#ff0000', accent: '#ffaa00' }, opacity: 1, emissive: true, emissiveIntensity: 1.0, wireframe: false, pulse: { enabled: true, frequency: 120, amplitude: 0.3, pattern: 'glitch' } },
490
+ },
491
+ threat_actor: {
492
+ geometry: { type: 'humanoid', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
493
+ material: { shader: 'threat', color: { base: '#880000', emissive: '#ff0000', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 90, amplitude: 0.2, pattern: 'glitch' } },
494
+ },
495
+ nation_state: {
496
+ geometry: { type: 'building', scale: { x: 4, y: 5, z: 4 }, baseScale: 6.0 },
497
+ material: { shader: 'threat', color: { base: '#660000', emissive: '#aa0000', accent: '#ffaa00' }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.1, pattern: 'heartbeat' } },
498
+ },
499
+
500
+ // Business entities (Patent Pillar D)
501
+ customer: {
502
+ geometry: { type: 'sphere', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
503
+ material: { shader: 'standard', color: { base: '#ffd700', emissive: '#ffaa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.1, pattern: 'heartbeat' } },
504
+ },
505
+ journey: {
506
+ geometry: { type: 'path', scale: { x: 10, y: 0.5, z: 2 }, baseScale: 1.0 },
507
+ material: { shader: 'energy', color: { base: '#00ffff', emissive: '#00aaaa', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'sine' } },
508
+ },
509
+ ticket: {
510
+ geometry: { type: 'cube', scale: { x: 1, y: 0.6, z: 0.1 }, baseScale: 0.8 },
511
+ material: { shader: 'standard', color: { base: '#ff8800', emissive: '#ff6600', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.15, pattern: 'heartbeat' } },
512
+ },
513
+
514
+ // AI entities
515
+ agent: {
516
+ geometry: { type: 'dodecahedron', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
517
+ material: { shader: 'energy', color: { base: '#ffd700', emissive: '#ffaa00', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.2, pattern: 'heartbeat' } },
518
+ },
519
+
520
+ // Generic
521
+ error: {
522
+ geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
523
+ material: { shader: 'error', color: { base: '#ff0040', emissive: '#ff0000', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 1.0, wireframe: false, pulse: { enabled: true, frequency: 120, amplitude: 0.3, pattern: 'glitch' } },
524
+ },
525
+ alert: {
526
+ geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.5, z: 1 }, baseScale: 1.2 },
527
+ material: { shader: 'standard', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 100, amplitude: 0.25, pattern: 'sine' } },
528
+ },
529
+ unknown: {
530
+ geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
531
+ material: { shader: 'standard', color: { base: '#888888', emissive: '#666666', accent: '#ffffff' }, opacity: 0.7, emissive: true, emissiveIntensity: 0.2, wireframe: true, pulse: { enabled: false, frequency: 0, amplitude: 0, pattern: 'sine' } },
532
+ },
533
+
534
+ // Fill in remaining types with sensible defaults
535
+ table: {
536
+ geometry: { type: 'cube', scale: { x: 1.5, y: 0.5, z: 1 }, baseScale: 1.2 },
537
+ material: { shader: 'hologram', color: { base: '#0088ff', emissive: '#0066cc', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.05, pattern: 'breath' } },
538
+ },
539
+ record: {
540
+ geometry: { type: 'cube', scale: { x: 0.8, y: 0.8, z: 0.2 }, baseScale: 0.6 },
541
+ material: { shader: 'standard', color: { base: '#00aaff', emissive: '#0088cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
542
+ },
543
+ firewall: {
544
+ geometry: { type: 'cube', scale: { x: 3, y: 2, z: 0.3 }, baseScale: 2.5 },
545
+ material: { shader: 'energy', color: { base: '#ff4400', emissive: '#ff2200', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 55, amplitude: 0.1, pattern: 'sine' } },
546
+ },
547
+ endpoint: {
548
+ geometry: { type: 'cube', scale: { x: 1, y: 0.7, z: 0.1 }, baseScale: 0.7 },
549
+ material: { shader: 'standard', color: { base: '#44aaff', emissive: '#2288cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.05, pattern: 'breath' } },
550
+ },
551
+ ip_address: {
552
+ geometry: { type: 'tetrahedron', scale: { x: 0.6, y: 0.6, z: 0.6 }, baseScale: 0.5 },
553
+ material: { shader: 'hologram', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.08, pattern: 'sine' } },
554
+ },
555
+ department: {
556
+ geometry: { type: 'building', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
557
+ material: { shader: 'standard', color: { base: '#6699ff', emissive: '#4477cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.03, pattern: 'breath' } },
558
+ },
559
+ team: {
560
+ geometry: { type: 'sphere', scale: { x: 1.2, y: 1.2, z: 1.2 }, baseScale: 1.5 },
561
+ material: { shader: 'standard', color: { base: '#88aaff', emissive: '#6688cc', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.05, pattern: 'breath' } },
562
+ },
563
+ campaign: {
564
+ geometry: { type: 'octahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
565
+ material: { shader: 'threat', color: { base: '#aa0000', emissive: '#ff0000', accent: '#ffaa00' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.15, pattern: 'glitch' } },
566
+ },
567
+ threat_group: {
568
+ geometry: { type: 'dodecahedron', scale: { x: 2, y: 2, z: 2 }, baseScale: 3.0 },
569
+ material: { shader: 'threat', color: { base: '#770000', emissive: '#cc0000', accent: '#ffff00' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.1, pattern: 'heartbeat' } },
570
+ },
571
+ touchpoint: {
572
+ geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.8 },
573
+ material: { shader: 'standard', color: { base: '#00ffaa', emissive: '#00cc88', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.1, pattern: 'sine' } },
574
+ },
575
+ sla: {
576
+ geometry: { type: 'torus', scale: { x: 1, y: 1, z: 0.3 }, baseScale: 1.0 },
577
+ material: { shader: 'energy', color: { base: '#00ff00', emissive: '#00aa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.15, pattern: 'heartbeat' } },
578
+ },
579
+ task: {
580
+ geometry: { type: 'cube', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.8 },
581
+ material: { shader: 'hologram', color: { base: '#00ffff', emissive: '#00aaaa', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.08, pattern: 'sine' } },
582
+ },
583
+ memory: {
584
+ geometry: { type: 'torus', scale: { x: 1, y: 1, z: 0.5 }, baseScale: 1.2 },
585
+ material: { shader: 'organic', color: { base: '#ff00ff', emissive: '#aa00aa', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.1, pattern: 'breath' } },
586
+ },
587
+
588
+ // =============================================================================
589
+ // GEMINI ADDITIONS - New Entity Types
590
+ // =============================================================================
591
+
592
+ // Network additions
593
+ firewall_policy: {
594
+ geometry: { type: 'cube', scale: { x: 2, y: 1.5, z: 0.2 }, baseScale: 1.8 },
595
+ material: { shader: 'shield_hex', color: { base: '#ff6600', emissive: '#ff4400', accent: '#ffff00' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 45, amplitude: 0.08, pattern: 'sine' } },
596
+ },
597
+ api_gateway: {
598
+ geometry: { type: 'octahedron', scale: { x: 1.5, y: 2, z: 1.5 }, baseScale: 2.2 },
599
+ material: { shader: 'hologram', color: { base: '#00ccff', emissive: '#0099cc', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 50, amplitude: 0.1, pattern: 'sine' } },
600
+ },
601
+ load_balancer: {
602
+ geometry: { type: 'torus', scale: { x: 2, y: 2, z: 0.5 }, baseScale: 2.0 },
603
+ material: { shader: 'energy', color: { base: '#44ff88', emissive: '#22cc66', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.12, pattern: 'heartbeat' } },
604
+ },
605
+
606
+ // Cloud/Container entities
607
+ container_pod: {
608
+ geometry: { type: 'cube', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.0 },
609
+ material: { shader: 'cloud_vapor', color: { base: '#326ce5', emissive: '#2558c5', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.06, pattern: 'breath' } },
610
+ },
611
+ kubernetes_cluster: {
612
+ geometry: { type: 'dodecahedron', scale: { x: 3, y: 3, z: 3 }, baseScale: 4.0 },
613
+ material: { shader: 'cloud_vapor', color: { base: '#326ce5', emissive: '#2558c5', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.08, pattern: 'breath' } },
614
+ },
615
+ cloud_function: {
616
+ geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.2, z: 1 }, baseScale: 1.0 },
617
+ material: { shader: 'cloud_vapor', color: { base: '#ff9900', emissive: '#cc7700', accent: '#ffffff' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 55, amplitude: 0.1, pattern: 'sine' } },
618
+ },
619
+
620
+ // Identity entities
621
+ identity_provider: {
622
+ geometry: { type: 'cylinder', scale: { x: 1.5, y: 2, z: 1.5 }, baseScale: 2.5 },
623
+ material: { shader: 'shield_hex', color: { base: '#9945ff', emissive: '#7733cc', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 35, amplitude: 0.06, pattern: 'breath' } },
624
+ },
625
+ service_account: {
626
+ geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.7 },
627
+ material: { shader: 'hologram', color: { base: '#aa88ff', emissive: '#8866cc', accent: '#ffffff' }, opacity: 0.85, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 40, amplitude: 0.05, pattern: 'breath' } },
628
+ },
629
+ certificate: {
630
+ geometry: { type: 'cube', scale: { x: 1.2, y: 0.8, z: 0.1 }, baseScale: 0.8 },
631
+ material: { shader: 'standard', color: { base: '#00ff88', emissive: '#00cc66', accent: '#ffd700' }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 25, amplitude: 0.03, pattern: 'breath' } },
632
+ },
633
+
634
+ // IoT entities
635
+ iot_device: {
636
+ geometry: { type: 'sphere', scale: { x: 0.6, y: 0.6, z: 0.6 }, baseScale: 0.5 },
637
+ material: { shader: 'pulse_rings', color: { base: '#00ff00', emissive: '#00aa00', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.6, wireframe: false, pulse: { enabled: true, frequency: 70, amplitude: 0.15, pattern: 'heartbeat' } },
638
+ },
639
+ sensor: {
640
+ geometry: { type: 'octahedron', scale: { x: 0.5, y: 0.7, z: 0.5 }, baseScale: 0.4 },
641
+ material: { shader: 'pulse_rings', color: { base: '#44ffaa', emissive: '#22cc88', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.2, pattern: 'sine' } },
642
+ },
643
+
644
+ // Security additions
645
+ vulnerability: {
646
+ geometry: { type: 'sphere', scale: { x: 0.8, y: 0.8, z: 0.8 }, baseScale: 0.9 },
647
+ material: { shader: 'glitch_noise_v2', color: { base: '#ffaa00', emissive: '#ff8800', accent: '#ff0000' }, opacity: 0.9, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 90, amplitude: 0.2, pattern: 'glitch' } },
648
+ },
649
+ exploit: {
650
+ geometry: { type: 'tetrahedron', scale: { x: 1, y: 1.5, z: 1 }, baseScale: 1.2 },
651
+ material: { shader: 'glitch_noise_v2', color: { base: '#ff0000', emissive: '#cc0000', accent: '#ffff00' }, opacity: 1, emissive: true, emissiveIntensity: 0.9, wireframe: false, pulse: { enabled: true, frequency: 110, amplitude: 0.25, pattern: 'glitch' } },
652
+ },
653
+
654
+ // Business additions
655
+ contract: {
656
+ geometry: { type: 'cube', scale: { x: 1.5, y: 1, z: 0.1 }, baseScale: 1.0 },
657
+ material: { shader: 'gold_metallic_clean', color: { base: '#ffd700', emissive: '#cc9900', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 20, amplitude: 0.02, pattern: 'breath' } },
658
+ },
659
+ invoice: {
660
+ geometry: { type: 'cube', scale: { x: 1.2, y: 0.8, z: 0.05 }, baseScale: 0.7 },
661
+ material: { shader: 'standard', color: { base: '#88ff88', emissive: '#66cc66', accent: '#ffffff', healthGradient: { healthy: '#00ff00', warning: '#ffaa00', critical: '#ff0000' } }, opacity: 1, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.04, pattern: 'breath' } },
662
+ },
663
+
664
+ // =============================================================================
665
+ // GEMINI: SEPARATION OF CONCERN - Server Split
666
+ // =============================================================================
667
+
668
+ compute_node: {
669
+ geometry: { type: 'octahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
670
+ material: { shader: 'energy', color: { base: '#ff6600', emissive: '#cc4400', accent: '#ffffff' }, opacity: 0.95, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 80, amplitude: 0.15, pattern: 'heartbeat' } },
671
+ },
672
+ storage_node: {
673
+ geometry: { type: 'cylinder', scale: { x: 1.2, y: 2, z: 1.2 }, baseScale: 2.2 },
674
+ material: { shader: 'matte_ceramic_hex', color: { base: '#2c3e50', emissive: '#00ff41', accent: '#ffffff' }, opacity: 1, emissive: true, emissiveIntensity: 0.4, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.05, pattern: 'breath' } },
675
+ },
676
+
677
+ // =============================================================================
678
+ // DEEPSEEK: COGNITIVE ENTITY TYPES
679
+ // =============================================================================
680
+
681
+ neural_node: {
682
+ geometry: { type: 'sphere', scale: { x: 1, y: 1, z: 1 }, baseScale: 1.2 },
683
+ material: { shader: 'energy', color: { base: '#3399ff', emissive: '#0066cc', accent: '#ffffff' }, opacity: 0.7, emissive: true, emissiveIntensity: 0.7, wireframe: false, pulse: { enabled: true, frequency: 150, amplitude: 0.1, pattern: 'sine' } },
684
+ },
685
+ synapse_connection: {
686
+ geometry: { type: 'cylinder', scale: { x: 0.1, y: 3, z: 0.1 }, baseScale: 1.0 },
687
+ material: { shader: 'flow_particle_stream', color: { base: '#ff6633', emissive: '#cc4400', accent: '#ffff00' }, opacity: 0.6, emissive: true, emissiveIntensity: 0.9, wireframe: false, pulse: { enabled: true, frequency: 480, amplitude: 0.2, pattern: 'sine' } },
688
+ },
689
+ concept_cluster: {
690
+ geometry: { type: 'dodecahedron', scale: { x: 2, y: 2, z: 2 }, baseScale: 2.5 },
691
+ material: { shader: 'organic', color: { base: '#66cc4d', emissive: '#44aa33', accent: '#ffffff' }, opacity: 0.6, emissive: true, emissiveIntensity: 0.5, wireframe: false, pulse: { enabled: true, frequency: 60, amplitude: 0.08, pattern: 'breath' } },
692
+ },
693
+ memory_trace: {
694
+ geometry: { type: 'torus', scale: { x: 1.5, y: 1.5, z: 0.3 }, baseScale: 1.5 },
695
+ material: { shader: 'hologram', color: { base: '#cc99e6', emissive: '#9966cc', accent: '#ffffff' }, opacity: 0.3, emissive: true, emissiveIntensity: 0.3, wireframe: false, pulse: { enabled: true, frequency: 30, amplitude: 0.15, pattern: 'breath' } },
696
+ },
697
+ metacognitive_monitor: {
698
+ geometry: { type: 'icosahedron', scale: { x: 1.5, y: 1.5, z: 1.5 }, baseScale: 2.0 },
699
+ material: { shader: 'gold_metallic_clean', color: { base: '#ffe633', emissive: '#ccb300', accent: '#ffffff' }, opacity: 0.8, emissive: true, emissiveIntensity: 0.8, wireframe: false, pulse: { enabled: true, frequency: 180, amplitude: 0.12, pattern: 'heartbeat' } },
700
+ },
701
+ };
702
+
703
+ // =============================================================================
704
+ // FACTORY FUNCTION
705
+ // =============================================================================
706
+
707
+ export function createVisualNode(
708
+ type: EntityType,
709
+ data: Partial<VisualNode>
710
+ ): VisualNode {
711
+ const defaults = ENTITY_TYPE_DEFAULTS[type] || ENTITY_TYPE_DEFAULTS.unknown;
712
+
713
+ return {
714
+ id: data.id || crypto.randomUUID(),
715
+ externalId: data.externalId,
716
+ type,
717
+ labels: data.labels || [type],
718
+ position: data.position || { x: 0, y: 0, z: 0 },
719
+ velocity: data.velocity || { x: 0, y: 0, z: 0 },
720
+ rotation: data.rotation || { x: 0, y: 0, z: 0 },
721
+ mass: data.mass || 1,
722
+ fixed: data.fixed || false,
723
+ collisionRadius: data.collisionRadius || 1,
724
+ geometry: { ...defaults.geometry, ...data.geometry } as GeometryConfig,
725
+ material: { ...defaults.material, ...data.material } as MaterialConfig,
726
+ animation: data.animation || getDefaultAnimation(),
727
+ layers: data.layers || getEmptyLayers(),
728
+ currentLayer: data.currentLayer || 'surface',
729
+ interaction: data.interaction || getDefaultInteraction(),
730
+ lod: data.lod || getDefaultLOD(),
731
+ parent: data.parent,
732
+ children: data.children || [],
733
+ createdAt: data.createdAt || new Date().toISOString(),
734
+ updatedAt: data.updatedAt || new Date().toISOString(),
735
+ };
736
+ }
737
+
738
+ function getDefaultAnimation(): AnimationConfig {
739
+ return {
740
+ idle: { rotation: true, rotationSpeed: 10, float: true, floatAmplitude: 0.1, floatSpeed: 0.5 },
741
+ hover: { scaleMultiplier: 1.1, glowIntensity: 0.3, duration: 200 },
742
+ select: { scaleMultiplier: 1.2, outlineColor: '#ffffff', outlineWidth: 2, duration: 150 },
743
+ peel: { duration: 500, fragmentCount: 12, fragmentFadeTime: 800, revealGlowDuration: 300, sound: 'peel_default' },
744
+ };
745
+ }
746
+
747
+ function getEmptyLayers(): OnionLayers {
748
+ return {
749
+ surface: { icon: '❓', title: 'Unknown', status: 'unknown', activityLevel: 0, importance: 50, badges: [] },
750
+ metadata: { fullName: '', description: '', category: '', tags: [], source: '', created: '', modified: '', customFields: {} },
751
+ connections: { inbound: [], outbound: [], bidirectional: [], totalConnections: 0 },
752
+ structure: { children: [], hierarchy: [], depth: 0, expandable: false },
753
+ core: { rawDataRef: undefined, rawDataPreview: undefined, origin: { system: 'unknown', primaryKey: '' }, audit: { createdBy: '', createdAt: '', history: [], version: 1 }, actions: [] },
754
+ };
755
+ }
756
+
757
+ function getDefaultInteraction(): InteractionState {
758
+ return {
759
+ hoverable: true,
760
+ selectable: true,
761
+ grabbable: true,
762
+ peelable: true,
763
+ expandable: true,
764
+ isHovered: false,
765
+ isSelected: false,
766
+ isGrabbed: false,
767
+ isPeeling: false,
768
+ };
769
+ }
770
+
771
+ function getDefaultLOD(): LODConfig {
772
+ return {
773
+ near: 5,
774
+ mid: 20,
775
+ far: 50,
776
+ nearGeometry: 'sphere',
777
+ midGeometry: 'octahedron',
778
+ farGeometry: 'point',
779
+ showLabelsAt: 15,
780
+ showConnectionsAt: 30,
781
+ };
782
+ }