@backbay/glia-three 0.2.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/package.json +43 -0
  2. package/src/environment/AuroraLayer/AuroraLayer.stories.tsx +43 -0
  3. package/src/environment/AuroraLayer/AuroraLayer.tsx +200 -0
  4. package/src/environment/AuroraLayer/index.ts +2 -0
  5. package/src/environment/AuroraLayer/types.ts +30 -0
  6. package/src/environment/EnvironmentLayer/EnvironmentLayer.stories.tsx +262 -0
  7. package/src/environment/EnvironmentLayer/EnvironmentLayer.tsx +105 -0
  8. package/src/environment/EnvironmentLayer/index.ts +4 -0
  9. package/src/environment/EnvironmentLayer/presets.ts +128 -0
  10. package/src/environment/FogLayer/FogLayer.stories.tsx +83 -0
  11. package/src/environment/FogLayer/FogLayer.tsx +113 -0
  12. package/src/environment/FogLayer/index.ts +2 -0
  13. package/src/environment/FogLayer/types.ts +45 -0
  14. package/src/environment/VolumetricLight/VolumetricLight.stories.tsx +55 -0
  15. package/src/environment/VolumetricLight/VolumetricLight.tsx +188 -0
  16. package/src/environment/VolumetricLight/index.ts +2 -0
  17. package/src/environment/VolumetricLight/types.ts +33 -0
  18. package/src/environment/WeatherLayer/WeatherLayer.stories.tsx +348 -0
  19. package/src/environment/WeatherLayer/WeatherLayer.tsx +266 -0
  20. package/src/environment/WeatherLayer/cinematicCanvas.tsx +809 -0
  21. package/src/environment/WeatherLayer/colors.ts +27 -0
  22. package/src/environment/WeatherLayer/index.ts +4 -0
  23. package/src/environment/WeatherLayer/leafPresets.ts +12 -0
  24. package/src/environment/WeatherLayer/particles.ts +227 -0
  25. package/src/environment/WeatherLayer/types.ts +140 -0
  26. package/src/environment/index.ts +17 -0
  27. package/src/environment/shared/index.ts +2 -0
  28. package/src/environment/shared/noise.ts +10 -0
  29. package/src/environment/shared/performance.ts +33 -0
  30. package/src/environment/shared/types.ts +26 -0
  31. package/src/index.ts +2 -0
  32. package/src/lib/utils.ts +6 -0
  33. package/src/lib/vision-types.ts +84 -0
  34. package/src/three/AgentConsole/AgentConsole.stories.tsx +397 -0
  35. package/src/three/AgentConsole/AgentConsole.tsx +195 -0
  36. package/src/three/AgentConsole/ConsoleChat.tsx +237 -0
  37. package/src/three/AgentConsole/FocusConstellation.tsx +297 -0
  38. package/src/three/AgentConsole/GlyphAvatar.stories.tsx +110 -0
  39. package/src/three/AgentConsole/GlyphAvatar.tsx +117 -0
  40. package/src/three/AgentConsole/QuickActions.tsx +111 -0
  41. package/src/three/AgentConsole/index.ts +41 -0
  42. package/src/three/AgentConsole/types.ts +241 -0
  43. package/src/three/AmbientField/AmbientField.stories.tsx +290 -0
  44. package/src/three/AmbientField/AmbientField.tsx +307 -0
  45. package/src/three/AmbientField/BackbayFieldBus.ts +326 -0
  46. package/src/three/AmbientField/FieldProvider.tsx +83 -0
  47. package/src/three/AmbientField/index.ts +37 -0
  48. package/src/three/AmbientField/shaders/constellation.ts +384 -0
  49. package/src/three/AmbientField/types.ts +174 -0
  50. package/src/three/AttackGraph/AttackGraph.stories.tsx +144 -0
  51. package/src/three/AttackGraph/AttackGraph.tsx +325 -0
  52. package/src/three/AttackGraph/index.ts +19 -0
  53. package/src/three/AttackGraph/types.ts +97 -0
  54. package/src/three/AuditTrail/AuditTrail.stories.tsx +567 -0
  55. package/src/three/AuditTrail/AuditTrail.tsx +644 -0
  56. package/src/three/AuditTrail/index.ts +33 -0
  57. package/src/three/AuditTrail/types.ts +192 -0
  58. package/src/three/CrystallineOrganism/Breadcrumb.tsx +61 -0
  59. package/src/three/CrystallineOrganism/CrystallineOrganism.stories.tsx +509 -0
  60. package/src/three/CrystallineOrganism/CrystallineOrganism.tsx +273 -0
  61. package/src/three/CrystallineOrganism/LatticeEdge.tsx +69 -0
  62. package/src/three/CrystallineOrganism/OrganismLattice.tsx +159 -0
  63. package/src/three/CrystallineOrganism/OrganismParticles.tsx +148 -0
  64. package/src/three/CrystallineOrganism/OrganismShell.tsx +277 -0
  65. package/src/three/CrystallineOrganism/constants.ts +161 -0
  66. package/src/three/CrystallineOrganism/index.ts +17 -0
  67. package/src/three/CrystallineOrganism/layouts/hexGrid.ts +85 -0
  68. package/src/three/CrystallineOrganism/layouts/index.ts +1 -0
  69. package/src/three/CrystallineOrganism/types.ts +167 -0
  70. package/src/three/CrystallineOrganism/useOrganismEmotion.ts +84 -0
  71. package/src/three/FirewallBarrier/FirewallBarrier.stories.tsx +167 -0
  72. package/src/three/FirewallBarrier/FirewallBarrier.tsx +259 -0
  73. package/src/three/FirewallBarrier/index.ts +14 -0
  74. package/src/three/FirewallBarrier/types.ts +52 -0
  75. package/src/three/Glyph/GlyphObject.stories.tsx +577 -0
  76. package/src/three/Glyph/GlyphObject.tsx +422 -0
  77. package/src/three/Glyph/index.ts +10 -0
  78. package/src/three/Glyph/types.ts +36 -0
  79. package/src/three/Glyph/useGlyphController.ts +231 -0
  80. package/src/three/Glyph/useGlyphEmotion.ts +70 -0
  81. package/src/three/Graph3D/Graph3D.stories.tsx +269 -0
  82. package/src/three/Graph3D/Graph3D.tsx +248 -0
  83. package/src/three/Graph3D/GraphEdge.tsx +79 -0
  84. package/src/three/Graph3D/GraphNode.tsx +239 -0
  85. package/src/three/Graph3D/types.ts +66 -0
  86. package/src/three/Graph3D/utils.ts +204 -0
  87. package/src/three/IntelFeed/IntelFeed.stories.tsx +168 -0
  88. package/src/three/IntelFeed/IntelFeed.tsx +284 -0
  89. package/src/three/IntelFeed/index.ts +14 -0
  90. package/src/three/IntelFeed/types.ts +56 -0
  91. package/src/three/MetricsGalaxy/MetricsGalaxy.tsx +484 -0
  92. package/src/three/MetricsGalaxy/index.ts +6 -0
  93. package/src/three/MetricsGalaxy/types.ts +26 -0
  94. package/src/three/NetworkTopology/NetworkTopology.stories.tsx +184 -0
  95. package/src/three/NetworkTopology/NetworkTopology.tsx +421 -0
  96. package/src/three/NetworkTopology/index.ts +34 -0
  97. package/src/three/NetworkTopology/types.ts +128 -0
  98. package/src/three/ParticleField/ParticleField.stories.tsx +162 -0
  99. package/src/three/ParticleField/ParticleField.tsx +81 -0
  100. package/src/three/ParticleField/index.ts +1 -0
  101. package/src/three/PermissionMatrix/PermissionMatrix.stories.tsx +475 -0
  102. package/src/three/PermissionMatrix/PermissionMatrix.tsx +380 -0
  103. package/src/three/PermissionMatrix/index.ts +15 -0
  104. package/src/three/PermissionMatrix/types.ts +54 -0
  105. package/src/three/QuantumField/ConstellationField.tsx +238 -0
  106. package/src/three/QuantumField/FieldBus.ts +349 -0
  107. package/src/three/QuantumField/FieldLayer.tsx +430 -0
  108. package/src/three/QuantumField/FieldProvider.tsx +460 -0
  109. package/src/three/QuantumField/PcbField.tsx +406 -0
  110. package/src/three/QuantumField/QuantumField.stories.tsx +1155 -0
  111. package/src/three/QuantumField/TrailRTT.ts +212 -0
  112. package/src/three/QuantumField/WaterField.tsx +226 -0
  113. package/src/three/QuantumField/WaterSimRTT.ts +283 -0
  114. package/src/three/QuantumField/domMapping.ts +185 -0
  115. package/src/three/QuantumField/index.ts +110 -0
  116. package/src/three/QuantumField/styles/index.ts +9 -0
  117. package/src/three/QuantumField/styles/styleA.ts +526 -0
  118. package/src/three/QuantumField/styles/styleB.ts +1210 -0
  119. package/src/three/QuantumField/styles/styleC.ts +266 -0
  120. package/src/three/QuantumField/themes.ts +211 -0
  121. package/src/three/QuantumField/types.ts +380 -0
  122. package/src/three/SOCCommandCenter/SOCCommandCenter.stories.tsx +591 -0
  123. package/src/three/SOCCommandCenter/SOCCommandCenter.tsx +248 -0
  124. package/src/three/SOCCommandCenter/index.ts +26 -0
  125. package/src/three/SOCCommandCenter/types.ts +201 -0
  126. package/src/three/SecurityDashboard/SecurityDashboard.stories.tsx +508 -0
  127. package/src/three/SecurityDashboard/SecurityDashboard.tsx +507 -0
  128. package/src/three/SecurityDashboard/index.ts +37 -0
  129. package/src/three/SecurityDashboard/types.ts +143 -0
  130. package/src/three/SecurityShield/SecurityShield.stories.tsx +257 -0
  131. package/src/three/SecurityShield/SecurityShield.tsx +502 -0
  132. package/src/three/SecurityShield/index.ts +25 -0
  133. package/src/three/SecurityShield/types.ts +64 -0
  134. package/src/three/Sentinel/AvatarMode.tsx +578 -0
  135. package/src/three/Sentinel/AvatarRenderer.tsx +199 -0
  136. package/src/three/Sentinel/CameraPip.tsx +127 -0
  137. package/src/three/Sentinel/CardinalItem.tsx +83 -0
  138. package/src/three/Sentinel/CardinalMenu.tsx +370 -0
  139. package/src/three/Sentinel/DockedMiniOrb.tsx +146 -0
  140. package/src/three/Sentinel/RadialSubmenu.tsx +273 -0
  141. package/src/three/Sentinel/SentinelConversation.tsx +802 -0
  142. package/src/three/Sentinel/SentinelOrb.tsx +316 -0
  143. package/src/three/Sentinel/SentinelOverlay.tsx +146 -0
  144. package/src/three/Sentinel/SentinelProvider.tsx +145 -0
  145. package/src/three/Sentinel/SentinelTether.tsx +182 -0
  146. package/src/three/Sentinel/SigilPlaceholder.tsx +176 -0
  147. package/src/three/Sentinel/VerticalSubmenu.tsx +150 -0
  148. package/src/three/Sentinel/index.ts +145 -0
  149. package/src/three/Sentinel/sentinelStore.ts +196 -0
  150. package/src/three/Sentinel/types.ts +403 -0
  151. package/src/three/Sentinel/useCameraPermission.ts +153 -0
  152. package/src/three/Sentinel/useThrowPhysics.ts +220 -0
  153. package/src/three/SpatialWorkspace/CyntraWorkspace.tsx +84 -0
  154. package/src/three/SpatialWorkspace/JobCluster.tsx +281 -0
  155. package/src/three/SpatialWorkspace/NodeGraph.tsx +236 -0
  156. package/src/three/SpatialWorkspace/ReceiptOrbit.tsx +368 -0
  157. package/src/three/SpatialWorkspace/SpatialWorkspace.stories.tsx +547 -0
  158. package/src/three/SpatialWorkspace/SpatialWorkspace.tsx +428 -0
  159. package/src/three/SpatialWorkspace/TrustRings.tsx +228 -0
  160. package/src/three/SpatialWorkspace/adapters.ts +353 -0
  161. package/src/three/SpatialWorkspace/index.ts +85 -0
  162. package/src/three/SpatialWorkspace/nexusAdapter.ts +182 -0
  163. package/src/three/SpatialWorkspace/types.ts +389 -0
  164. package/src/three/ThreatRadar/ThreatRadar.stories.tsx +451 -0
  165. package/src/three/ThreatRadar/ThreatRadar.tsx +542 -0
  166. package/src/three/ThreatRadar/index.ts +8 -0
  167. package/src/three/ThreatRadar/types.ts +90 -0
  168. package/src/three/ThreeErrorBoundary/ThreeErrorBoundary.tsx +235 -0
  169. package/src/three/ThreeErrorBoundary/index.ts +5 -0
  170. package/src/three/index.ts +56 -0
  171. package/tsconfig.json +20 -0
  172. package/tsup.config.ts +21 -0
@@ -0,0 +1,430 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Quantum Field Canvas - Field Layer Component
5
+ *
6
+ * The main R3F canvas component that renders the reactive field substrate.
7
+ * Supports multiple visual styles: constellation (A), pcb (B), water (C).
8
+ */
9
+
10
+ import { Canvas, useFrame, useThree } from "@react-three/fiber";
11
+ import React, { Suspense, useCallback, useEffect, useMemo, useRef } from "react";
12
+ import * as THREE from "three";
13
+ import { ConstellationField } from "./ConstellationField";
14
+ import type { FieldBus } from "./FieldBus";
15
+ import { createFieldBus } from "./FieldBus";
16
+ import { clientToNdc } from "./domMapping";
17
+ import { FieldProvider, useFieldBus } from "./FieldProvider";
18
+ import { PcbField } from "./PcbField";
19
+ import type { FieldConfig, FieldRuntimeState } from "./types";
20
+ import { DEFAULT_FIELD_CONFIG } from "./types";
21
+ import { WaterField } from "./WaterField";
22
+
23
+ // -----------------------------------------------------------------------------
24
+ // Debug Shader Material
25
+ // -----------------------------------------------------------------------------
26
+
27
+ const DEBUG_VERTEX_SHADER = /* glsl */ `
28
+ varying vec2 vUv;
29
+
30
+ void main() {
31
+ vUv = uv;
32
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
33
+ }
34
+ `;
35
+
36
+ const DEBUG_FRAGMENT_SHADER = /* glsl */ `
37
+ uniform float uTime;
38
+ uniform vec4 uHover; // xy = uv, z = intent (0=probe, 1=etch), w = active
39
+ uniform vec4 uImpulses[24]; // xy = uv, z = radius, w = amp
40
+ uniform float uImpulsesAge[24]; // normalized age (0-1, 1 = expired)
41
+ uniform int uImpulseCount;
42
+ uniform vec4 uAnchors[8]; // xy = uv, z = strength, w = phase
43
+ uniform int uAnchorCount;
44
+
45
+ varying vec2 vUv;
46
+
47
+ // Subtle grid pattern
48
+ float grid(vec2 uv, float spacing) {
49
+ vec2 g = abs(fract(uv * spacing - 0.5) - 0.5) / fwidth(uv * spacing);
50
+ return 1.0 - min(min(g.x, g.y), 1.0);
51
+ }
52
+
53
+ // Smooth circle
54
+ float circle(vec2 uv, vec2 center, float radius, float softness) {
55
+ float d = length(uv - center);
56
+ return 1.0 - smoothstep(radius - softness, radius + softness, d);
57
+ }
58
+
59
+ // Ring shape
60
+ float ring(vec2 uv, vec2 center, float radius, float thickness, float softness) {
61
+ float d = length(uv - center);
62
+ float inner = smoothstep(radius - thickness - softness, radius - thickness, d);
63
+ float outer = 1.0 - smoothstep(radius, radius + softness, d);
64
+ return inner * outer;
65
+ }
66
+
67
+ void main() {
68
+ vec2 uv = vUv;
69
+
70
+ // Base: very subtle dark with faint grid
71
+ vec3 baseColor = vec3(0.02, 0.03, 0.05);
72
+ float gridVal = grid(uv, 20.0) * 0.03;
73
+ vec3 color = baseColor + vec3(gridVal);
74
+
75
+ // Hover probe effect
76
+ if (uHover.w > 0.5) {
77
+ vec2 hoverUv = uHover.xy;
78
+ float probeRadius = 0.08;
79
+ float intensity = uHover.z > 0.5 ? 0.4 : 0.25; // etch vs probe
80
+
81
+ float probe = circle(uv, hoverUv, probeRadius, 0.02);
82
+ vec3 probeColor = uHover.z > 0.5
83
+ ? vec3(0.9, 0.2, 0.5) // magenta for etch
84
+ : vec3(0.1, 0.8, 0.9); // cyan for probe
85
+
86
+ color += probeColor * probe * intensity;
87
+
88
+ // Add subtle crackle near probe
89
+ float crackle = fract(sin(dot(uv * 100.0 + uTime * 5.0, vec2(12.9898, 78.233))) * 43758.5453);
90
+ float crackleZone = circle(uv, hoverUv, probeRadius * 1.5, 0.05);
91
+ color += vec3(0.1, 0.6, 0.7) * crackle * crackleZone * 0.1;
92
+ }
93
+
94
+ // Impulse rings (bursts)
95
+ for (int i = 0; i < 24; i++) {
96
+ if (i >= uImpulseCount) break;
97
+
98
+ vec4 imp = uImpulses[i];
99
+ float age = uImpulsesAge[i];
100
+
101
+ if (age >= 1.0) continue;
102
+
103
+ vec2 impUv = imp.xy;
104
+ float baseRadius = imp.z;
105
+ float amp = imp.w;
106
+
107
+ // Expanding ring
108
+ float expandedRadius = baseRadius + age * 0.3;
109
+ float thickness = 0.01 + (1.0 - age) * 0.02;
110
+ float ringVal = ring(uv, impUv, expandedRadius, thickness, 0.005);
111
+
112
+ // Fade out with age
113
+ float fade = (1.0 - age) * amp;
114
+
115
+ // Interference pattern
116
+ float interference = sin((length(uv - impUv) - uTime * 0.5) * 60.0) * 0.5 + 0.5;
117
+
118
+ vec3 ringColor = mix(
119
+ vec3(0.2, 0.9, 1.0), // cyan
120
+ vec3(1.0, 0.3, 0.6), // magenta
121
+ interference
122
+ );
123
+
124
+ color += ringColor * ringVal * fade;
125
+ }
126
+
127
+ // Anchors (persistent glowing nodes)
128
+ for (int i = 0; i < 8; i++) {
129
+ if (i >= uAnchorCount) break;
130
+
131
+ vec4 anchor = uAnchors[i];
132
+ vec2 anchorUv = anchor.xy;
133
+ float strength = anchor.z;
134
+ float phase = anchor.w;
135
+
136
+ // Pulsing glow
137
+ float pulse = 0.7 + 0.3 * sin(uTime * 3.0 + phase);
138
+ float anchorGlow = circle(uv, anchorUv, 0.03, 0.02) * strength * pulse;
139
+
140
+ // Tether-like radial streak
141
+ vec2 toAnchor = anchorUv - uv;
142
+ float dist = length(toAnchor);
143
+ float angle = atan(toAnchor.y, toAnchor.x);
144
+ float streak = smoothstep(0.1, 0.0, dist) * (0.5 + 0.5 * sin(angle * 8.0 + uTime * 2.0));
145
+
146
+ color += vec3(0.3, 0.9, 0.4) * anchorGlow; // emerald
147
+ color += vec3(0.2, 0.6, 0.3) * streak * 0.1 * strength;
148
+ }
149
+
150
+ gl_FragColor = vec4(color, 1.0);
151
+ }
152
+ `;
153
+
154
+ // -----------------------------------------------------------------------------
155
+ // Field Plane Component (inside Canvas)
156
+ // -----------------------------------------------------------------------------
157
+
158
+ interface FieldPlaneProps {
159
+ bus: FieldBus;
160
+ config: FieldConfig;
161
+ }
162
+
163
+ // Reusable raycast objects
164
+ const _raycaster = new THREE.Raycaster();
165
+ const _ndcVec = new THREE.Vector2();
166
+ const _intersections: THREE.Intersection[] = [];
167
+
168
+ function FieldPlane({ bus, config }: FieldPlaneProps) {
169
+ const { camera, size, gl } = useThree();
170
+ const meshRef = useRef<THREE.Mesh>(null);
171
+
172
+ // Local refs for state (avoid re-renders)
173
+ const stateRef = useRef<FieldRuntimeState>(bus.getSnapshot());
174
+
175
+ // Subscribe to bus updates
176
+ useEffect(() => {
177
+ const unsub = bus.subscribe((state) => {
178
+ stateRef.current = state;
179
+ });
180
+ return unsub;
181
+ }, [bus]);
182
+
183
+ // UV computation via raycast
184
+ const computeUv = useCallback(
185
+ (clientX: number, clientY: number): { x: number; y: number } | null => {
186
+ if (!meshRef.current) return null;
187
+
188
+ // Get canvas bounds
189
+ const canvas = gl.domElement;
190
+ const rect = canvas.getBoundingClientRect();
191
+
192
+ // Convert client coords to NDC relative to canvas
193
+ const ndc = clientToNdc(clientX - rect.left, clientY - rect.top, {
194
+ width: rect.width,
195
+ height: rect.height,
196
+ });
197
+
198
+ _ndcVec.set(ndc.x, ndc.y);
199
+ _raycaster.setFromCamera(_ndcVec, camera);
200
+
201
+ _intersections.length = 0;
202
+ meshRef.current.raycast(_raycaster, _intersections);
203
+
204
+ if (_intersections.length > 0 && _intersections[0].uv) {
205
+ return {
206
+ x: _intersections[0].uv.x,
207
+ y: _intersections[0].uv.y,
208
+ };
209
+ }
210
+
211
+ return null;
212
+ },
213
+ [camera, gl.domElement]
214
+ );
215
+
216
+ // Create uniforms
217
+ const uniforms = useMemo(
218
+ () => ({
219
+ uTime: { value: 0 },
220
+ uHover: { value: new THREE.Vector4(0.5, 0.5, 0, 0) },
221
+ uImpulses: {
222
+ value: Array(24)
223
+ .fill(null)
224
+ .map(() => new THREE.Vector4(0, 0, 0, 0)),
225
+ },
226
+ uImpulsesAge: { value: new Float32Array(24) },
227
+ uImpulseCount: { value: 0 },
228
+ uAnchors: {
229
+ value: Array(8)
230
+ .fill(null)
231
+ .map(() => new THREE.Vector4(0, 0, 0, 0)),
232
+ },
233
+ uAnchorCount: { value: 0 },
234
+ }),
235
+ []
236
+ );
237
+
238
+ // Create shader material
239
+ const material = useMemo(() => {
240
+ return new THREE.ShaderMaterial({
241
+ vertexShader: DEBUG_VERTEX_SHADER,
242
+ fragmentShader: DEBUG_FRAGMENT_SHADER,
243
+ uniforms,
244
+ transparent: true,
245
+ });
246
+ }, [uniforms]);
247
+
248
+ // Calculate plane size based on camera
249
+ const planeSize = useMemo(() => {
250
+ if (camera instanceof THREE.PerspectiveCamera) {
251
+ const distance = camera.position.z;
252
+ const vFov = (camera.fov * Math.PI) / 180;
253
+ const height = 2 * Math.tan(vFov / 2) * distance;
254
+ const width = height * (size.width / size.height);
255
+ return { width: width * 1.2, height: height * 1.2 };
256
+ }
257
+ return { width: 20, height: 15 };
258
+ }, [camera, size]);
259
+
260
+ // Animation frame
261
+ useFrame((state, delta) => {
262
+ if (!meshRef.current) return;
263
+
264
+ const currentTs = Date.now();
265
+ const currentState = stateRef.current;
266
+
267
+ // Process pending events with raycast UV computation
268
+ bus.processPendingWithUv(computeUv);
269
+
270
+ // Tick the bus
271
+ bus.tick(delta * 1000, currentTs);
272
+
273
+ // Update time
274
+ material.uniforms.uTime.value = state.clock.elapsedTime;
275
+
276
+ // Update hover uniform
277
+ if (currentState.hover.active) {
278
+ // Convert last known client position to UV via raycast
279
+ const hover = currentState.hover;
280
+ material.uniforms.uHover.value.set(
281
+ hover.uv.x,
282
+ hover.uv.y,
283
+ hover.intent === "etch" ? 1 : 0,
284
+ 1
285
+ );
286
+ } else {
287
+ material.uniforms.uHover.value.w = 0;
288
+ }
289
+
290
+ // Update impulses
291
+ const impulses = currentState.impulses;
292
+ material.uniforms.uImpulseCount.value = impulses.length;
293
+
294
+ for (let i = 0; i < 24; i++) {
295
+ if (i < impulses.length) {
296
+ const imp = impulses[i];
297
+ const age = Math.min(1, (currentTs - imp.startTs) / config.impulseDecayMs);
298
+ material.uniforms.uImpulses.value[i].set(imp.uv.x, imp.uv.y, imp.radius, imp.amplitude);
299
+ material.uniforms.uImpulsesAge.value[i] = age;
300
+ } else {
301
+ material.uniforms.uImpulsesAge.value[i] = 1;
302
+ }
303
+ }
304
+
305
+ // Update anchors
306
+ const anchors = Array.from(currentState.anchors.values());
307
+ material.uniforms.uAnchorCount.value = anchors.length;
308
+
309
+ for (let i = 0; i < 8; i++) {
310
+ if (i < anchors.length) {
311
+ const anchor = anchors[i];
312
+ const phase = (currentTs - anchor.createdTs) * 0.001;
313
+ material.uniforms.uAnchors.value[i].set(anchor.uv.x, anchor.uv.y, anchor.strength, phase);
314
+ }
315
+ }
316
+ });
317
+
318
+ // Dispose material on unmount
319
+ useEffect(() => {
320
+ return () => {
321
+ material.dispose();
322
+ };
323
+ }, [material]);
324
+
325
+ return (
326
+ <mesh ref={meshRef} position={[0, 0, 0]} material={material}>
327
+ <planeGeometry args={[planeSize.width, planeSize.height]} />
328
+ </mesh>
329
+ );
330
+ }
331
+
332
+ // -----------------------------------------------------------------------------
333
+ // Main Field Layer Component
334
+ // -----------------------------------------------------------------------------
335
+
336
+ export interface FieldLayerProps {
337
+ /** Custom configuration overrides */
338
+ config?: Partial<FieldConfig>;
339
+ /** Custom class for the wrapper */
340
+ className?: string;
341
+ /** Whether to pin to viewport (fixed) or fill parent (absolute) */
342
+ pinToViewport?: boolean;
343
+ /** Z-index for the canvas wrapper */
344
+ zIndex?: number;
345
+ /** Camera FOV */
346
+ fov?: number;
347
+ /** Camera Z position */
348
+ cameraZ?: number;
349
+ }
350
+
351
+ export function FieldLayer({
352
+ config: configOverrides,
353
+ className,
354
+ pinToViewport = true,
355
+ zIndex = -1,
356
+ fov = 50,
357
+ cameraZ = 10,
358
+ }: FieldLayerProps) {
359
+ const bus = useFieldBus();
360
+ const canvasRef = useRef<HTMLCanvasElement>(null);
361
+ const wrapperRef = useRef<HTMLDivElement>(null);
362
+
363
+ // Merge config
364
+ const config = useMemo(
365
+ () => ({ ...DEFAULT_FIELD_CONFIG, ...configOverrides }),
366
+ [configOverrides]
367
+ );
368
+
369
+ // Update bus config when it changes
370
+ useEffect(() => {
371
+ bus.setConfig(config);
372
+ }, [bus, config]);
373
+
374
+ const wrapperStyle: React.CSSProperties = {
375
+ position: pinToViewport ? "fixed" : "absolute",
376
+ inset: 0,
377
+ zIndex,
378
+ pointerEvents: "none",
379
+ overflow: "hidden",
380
+ };
381
+
382
+ return (
383
+ <div ref={wrapperRef} className={className} style={wrapperStyle} data-field-layer>
384
+ <Canvas
385
+ ref={canvasRef}
386
+ camera={{ position: [0, 0, cameraZ], fov }}
387
+ dpr={config.dpr}
388
+ gl={{ antialias: true, alpha: true }}
389
+ style={{ background: "transparent" }}
390
+ >
391
+ <Suspense fallback={null}>
392
+ {config.style === "constellation" ? (
393
+ <ConstellationField bus={bus} config={config} />
394
+ ) : config.style === "pcb" ? (
395
+ <PcbField bus={bus} config={config} />
396
+ ) : config.style === "water" ? (
397
+ <WaterField bus={bus} config={config} />
398
+ ) : (
399
+ // Fallback to debug plane
400
+ <FieldPlane bus={bus} config={config} />
401
+ )}
402
+ </Suspense>
403
+ </Canvas>
404
+ </div>
405
+ );
406
+ }
407
+
408
+ // -----------------------------------------------------------------------------
409
+ // Standalone Field Layer (includes its own provider)
410
+ // -----------------------------------------------------------------------------
411
+
412
+ export interface StandaloneFieldLayerProps extends FieldLayerProps {
413
+ /** Initial bus configuration */
414
+ busConfig?: Partial<FieldConfig>;
415
+ }
416
+
417
+ export function StandaloneFieldLayer({ busConfig, ...props }: StandaloneFieldLayerProps) {
418
+ const busRef = useRef<FieldBus | null>(null);
419
+
420
+ // Create bus once
421
+ if (!busRef.current) {
422
+ busRef.current = createFieldBus(busConfig);
423
+ }
424
+
425
+ return (
426
+ <FieldProvider bus={busRef.current}>
427
+ <FieldLayer {...props} />
428
+ </FieldProvider>
429
+ );
430
+ }