spoint 0.1.0 → 0.1.10

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 (204) hide show
  1. package/README.md +134 -209
  2. package/SKILL.md +95 -0
  3. package/apps/environment/index.js +200 -1
  4. package/apps/environment/models/decorative/.gitkeep +0 -0
  5. package/apps/environment/models/hazards/.gitkeep +0 -0
  6. package/apps/environment/models/interactive/.gitkeep +0 -0
  7. package/apps/environment/models/structures/.gitkeep +0 -0
  8. package/apps/environment/smartObjects.js +114 -0
  9. package/apps/interactable/index.js +155 -0
  10. package/apps/physics-crate/index.js +15 -9
  11. package/apps/power-crate/index.js +18 -12
  12. package/apps/tps-game/$GDUPI.vrm +0 -0
  13. package/apps/tps-game/Cleetus.vrm +0 -0
  14. package/apps/tps-game/index.js +185 -27
  15. package/apps/world/index.js +68 -22
  16. package/bin/create-app.js +337 -0
  17. package/client/ARControls.js +301 -0
  18. package/client/LoadingManager.js +117 -0
  19. package/client/MobileControls.js +1122 -0
  20. package/client/anim-lib.glb +0 -0
  21. package/client/animation.js +306 -0
  22. package/client/app.js +1341 -65
  23. package/client/camera.js +191 -33
  24. package/client/createLoadingScreen.js +69 -0
  25. package/client/editor/bridge.js +113 -0
  26. package/client/editor/css/main.css +794 -0
  27. package/client/editor/images/rotate.svg +4 -0
  28. package/client/editor/images/scale.svg +4 -0
  29. package/client/editor/images/translate.svg +4 -0
  30. package/client/editor/index.html +103 -0
  31. package/client/editor/js/Command.js +41 -0
  32. package/client/editor/js/Config.js +81 -0
  33. package/client/editor/js/Editor.js +785 -0
  34. package/client/editor/js/EditorControls.js +438 -0
  35. package/client/editor/js/History.js +321 -0
  36. package/client/editor/js/Loader.js +987 -0
  37. package/client/editor/js/LoaderUtils.js +90 -0
  38. package/client/editor/js/Menubar.Add.js +510 -0
  39. package/client/editor/js/Menubar.Edit.js +145 -0
  40. package/client/editor/js/Menubar.File.js +466 -0
  41. package/client/editor/js/Menubar.Help.js +73 -0
  42. package/client/editor/js/Menubar.Status.js +51 -0
  43. package/client/editor/js/Menubar.View.js +183 -0
  44. package/client/editor/js/Menubar.js +27 -0
  45. package/client/editor/js/Player.js +53 -0
  46. package/client/editor/js/Resizer.js +58 -0
  47. package/client/editor/js/Script.js +503 -0
  48. package/client/editor/js/Selector.js +102 -0
  49. package/client/editor/js/Sidebar.Geometry.BoxGeometry.js +121 -0
  50. package/client/editor/js/Sidebar.Geometry.BufferGeometry.js +115 -0
  51. package/client/editor/js/Sidebar.Geometry.CapsuleGeometry.js +97 -0
  52. package/client/editor/js/Sidebar.Geometry.CircleGeometry.js +97 -0
  53. package/client/editor/js/Sidebar.Geometry.CylinderGeometry.js +121 -0
  54. package/client/editor/js/Sidebar.Geometry.DodecahedronGeometry.js +73 -0
  55. package/client/editor/js/Sidebar.Geometry.ExtrudeGeometry.js +196 -0
  56. package/client/editor/js/Sidebar.Geometry.IcosahedronGeometry.js +73 -0
  57. package/client/editor/js/Sidebar.Geometry.LatheGeometry.js +98 -0
  58. package/client/editor/js/Sidebar.Geometry.Modifiers.js +73 -0
  59. package/client/editor/js/Sidebar.Geometry.OctahedronGeometry.js +74 -0
  60. package/client/editor/js/Sidebar.Geometry.PlaneGeometry.js +97 -0
  61. package/client/editor/js/Sidebar.Geometry.RingGeometry.js +121 -0
  62. package/client/editor/js/Sidebar.Geometry.ShapeGeometry.js +76 -0
  63. package/client/editor/js/Sidebar.Geometry.SphereGeometry.js +133 -0
  64. package/client/editor/js/Sidebar.Geometry.TetrahedronGeometry.js +74 -0
  65. package/client/editor/js/Sidebar.Geometry.TorusGeometry.js +109 -0
  66. package/client/editor/js/Sidebar.Geometry.TorusKnotGeometry.js +121 -0
  67. package/client/editor/js/Sidebar.Geometry.TubeGeometry.js +135 -0
  68. package/client/editor/js/Sidebar.Geometry.js +332 -0
  69. package/client/editor/js/Sidebar.Material.BooleanProperty.js +60 -0
  70. package/client/editor/js/Sidebar.Material.ColorProperty.js +87 -0
  71. package/client/editor/js/Sidebar.Material.ConstantProperty.js +62 -0
  72. package/client/editor/js/Sidebar.Material.MapProperty.js +249 -0
  73. package/client/editor/js/Sidebar.Material.NumberProperty.js +60 -0
  74. package/client/editor/js/Sidebar.Material.Program.js +73 -0
  75. package/client/editor/js/Sidebar.Material.RangeValueProperty.js +63 -0
  76. package/client/editor/js/Sidebar.Material.js +751 -0
  77. package/client/editor/js/Sidebar.Object.Animation.js +102 -0
  78. package/client/editor/js/Sidebar.Object.js +898 -0
  79. package/client/editor/js/Sidebar.Project.App.js +165 -0
  80. package/client/editor/js/Sidebar.Project.Image.js +225 -0
  81. package/client/editor/js/Sidebar.Project.Materials.js +82 -0
  82. package/client/editor/js/Sidebar.Project.Renderer.js +144 -0
  83. package/client/editor/js/Sidebar.Project.Video.js +242 -0
  84. package/client/editor/js/Sidebar.Project.js +31 -0
  85. package/client/editor/js/Sidebar.Properties.js +73 -0
  86. package/client/editor/js/Sidebar.Scene.js +585 -0
  87. package/client/editor/js/Sidebar.Script.js +129 -0
  88. package/client/editor/js/Sidebar.Settings.History.js +146 -0
  89. package/client/editor/js/Sidebar.Settings.Shortcuts.js +175 -0
  90. package/client/editor/js/Sidebar.Settings.js +60 -0
  91. package/client/editor/js/Sidebar.js +41 -0
  92. package/client/editor/js/Storage.js +98 -0
  93. package/client/editor/js/Strings.js +2028 -0
  94. package/client/editor/js/Toolbar.js +84 -0
  95. package/client/editor/js/Viewport.Controls.js +92 -0
  96. package/client/editor/js/Viewport.Info.js +136 -0
  97. package/client/editor/js/Viewport.Pathtracer.js +91 -0
  98. package/client/editor/js/Viewport.ViewHelper.js +39 -0
  99. package/client/editor/js/Viewport.XR.js +222 -0
  100. package/client/editor/js/Viewport.js +900 -0
  101. package/client/editor/js/commands/AddObjectCommand.js +68 -0
  102. package/client/editor/js/commands/AddScriptCommand.js +75 -0
  103. package/client/editor/js/commands/Commands.js +23 -0
  104. package/client/editor/js/commands/MoveObjectCommand.js +111 -0
  105. package/client/editor/js/commands/MultiCmdsCommand.js +85 -0
  106. package/client/editor/js/commands/RemoveObjectCommand.js +88 -0
  107. package/client/editor/js/commands/RemoveScriptCommand.js +81 -0
  108. package/client/editor/js/commands/SetColorCommand.js +73 -0
  109. package/client/editor/js/commands/SetGeometryCommand.js +87 -0
  110. package/client/editor/js/commands/SetGeometryValueCommand.js +70 -0
  111. package/client/editor/js/commands/SetMaterialColorCommand.js +86 -0
  112. package/client/editor/js/commands/SetMaterialCommand.js +79 -0
  113. package/client/editor/js/commands/SetMaterialMapCommand.js +143 -0
  114. package/client/editor/js/commands/SetMaterialRangeCommand.js +91 -0
  115. package/client/editor/js/commands/SetMaterialValueCommand.js +90 -0
  116. package/client/editor/js/commands/SetMaterialVectorCommand.js +79 -0
  117. package/client/editor/js/commands/SetPositionCommand.js +84 -0
  118. package/client/editor/js/commands/SetRotationCommand.js +84 -0
  119. package/client/editor/js/commands/SetScaleCommand.js +84 -0
  120. package/client/editor/js/commands/SetSceneCommand.js +103 -0
  121. package/client/editor/js/commands/SetScriptValueCommand.js +80 -0
  122. package/client/editor/js/commands/SetShadowValueCommand.js +73 -0
  123. package/client/editor/js/commands/SetUuidCommand.js +70 -0
  124. package/client/editor/js/commands/SetValueCommand.js +75 -0
  125. package/client/editor/js/libs/acorn/acorn.js +3236 -0
  126. package/client/editor/js/libs/acorn/acorn_loose.js +1299 -0
  127. package/client/editor/js/libs/acorn/walk.js +344 -0
  128. package/client/editor/js/libs/app/index.html +57 -0
  129. package/client/editor/js/libs/app.js +251 -0
  130. package/client/editor/js/libs/codemirror/addon/dialog.css +32 -0
  131. package/client/editor/js/libs/codemirror/addon/dialog.js +163 -0
  132. package/client/editor/js/libs/codemirror/addon/show-hint.css +36 -0
  133. package/client/editor/js/libs/codemirror/addon/show-hint.js +529 -0
  134. package/client/editor/js/libs/codemirror/addon/tern.css +87 -0
  135. package/client/editor/js/libs/codemirror/addon/tern.js +750 -0
  136. package/client/editor/js/libs/codemirror/codemirror.css +344 -0
  137. package/client/editor/js/libs/codemirror/codemirror.js +9849 -0
  138. package/client/editor/js/libs/codemirror/mode/glsl.js +233 -0
  139. package/client/editor/js/libs/codemirror/mode/javascript.js +959 -0
  140. package/client/editor/js/libs/codemirror/theme/monokai.css +41 -0
  141. package/client/editor/js/libs/esprima.js +6401 -0
  142. package/client/editor/js/libs/jsonlint.js +453 -0
  143. package/client/editor/js/libs/signals.min.js +14 -0
  144. package/client/editor/js/libs/tern-threejs/threejs.js +5031 -0
  145. package/client/editor/js/libs/ternjs/comment.js +87 -0
  146. package/client/editor/js/libs/ternjs/def.js +588 -0
  147. package/client/editor/js/libs/ternjs/doc_comment.js +401 -0
  148. package/client/editor/js/libs/ternjs/infer.js +1635 -0
  149. package/client/editor/js/libs/ternjs/polyfill.js +80 -0
  150. package/client/editor/js/libs/ternjs/signal.js +26 -0
  151. package/client/editor/js/libs/ternjs/tern.js +993 -0
  152. package/client/editor/js/libs/ui.js +1346 -0
  153. package/client/editor/js/libs/ui.three.js +855 -0
  154. package/client/facial-animation.js +455 -0
  155. package/client/index.html +7 -4
  156. package/client/loading.css +147 -0
  157. package/client/loading.html +25 -0
  158. package/client/style.css +251 -0
  159. package/package.json +7 -3
  160. package/server.js +9 -1
  161. package/src/apps/AppContext.js +1 -1
  162. package/src/apps/AppLoader.js +50 -37
  163. package/src/apps/AppRuntime.js +32 -8
  164. package/src/client/InputHandler.js +233 -0
  165. package/src/client/JitterBuffer.js +207 -0
  166. package/src/client/KalmanFilter.js +125 -0
  167. package/src/client/MessageHandler.js +101 -0
  168. package/src/client/PhysicsNetworkClient.js +141 -68
  169. package/src/client/ReconnectManager.js +62 -0
  170. package/src/client/SmoothInterpolation.js +127 -0
  171. package/src/client/SnapshotProcessor.js +144 -0
  172. package/src/connection/ConnectionManager.js +21 -3
  173. package/src/connection/SessionStore.js +13 -3
  174. package/src/index.client.js +4 -6
  175. package/src/netcode/EventLog.js +29 -15
  176. package/src/netcode/LagCompensator.js +25 -26
  177. package/src/netcode/NetworkState.js +4 -1
  178. package/src/netcode/PhysicsIntegration.js +20 -6
  179. package/src/netcode/PlayerManager.js +10 -2
  180. package/src/netcode/SnapshotEncoder.js +66 -19
  181. package/src/netcode/TickSystem.js +13 -4
  182. package/src/physics/World.js +66 -13
  183. package/src/protocol/msgpack.js +90 -63
  184. package/src/sdk/ReloadHandlers.js +12 -2
  185. package/src/sdk/ReloadManager.js +5 -0
  186. package/src/sdk/ServerHandlers.js +50 -11
  187. package/src/sdk/StaticHandler.js +22 -6
  188. package/src/sdk/TickHandler.js +101 -34
  189. package/src/sdk/scaffold.js +28 -0
  190. package/src/sdk/server.js +59 -33
  191. package/src/shared/movement.js +2 -1
  192. package/src/spatial/Octree.js +5 -0
  193. package/apps/interactive-door/index.js +0 -33
  194. package/apps/patrol-npc/index.js +0 -37
  195. package/src/connection/QualityMonitor.js +0 -46
  196. package/src/debug/StateInspector.js +0 -42
  197. package/src/index.js +0 -1
  198. package/src/index.server.js +0 -27
  199. package/src/protocol/Codec.js +0 -60
  200. package/src/protocol/SequenceTracker.js +0 -71
  201. package/src/sdk/ClientMessageHandler.js +0 -80
  202. package/src/sdk/client.js +0 -122
  203. package/world/kaira.glb +0 -0
  204. package/world/schwust.glb +0 -0
@@ -6,6 +6,20 @@ export class InputHandler {
6
6
  this.mouseDown = false
7
7
  this.callbacks = []
8
8
  this.enabled = true
9
+ this.renderer = config.renderer || null
10
+ this.vrYaw = 0
11
+ this.vrPitch = 0
12
+ this.vrPitchDelta = 0
13
+ this.vrYawDelta = 0
14
+ this.snapCooldown = false
15
+ this.snapTurnAngle = config.snapTurnAngle || 30
16
+ this.smoothTurnSpeed = config.smoothTurnSpeed || 0
17
+ this.onMenuPressed = config.onMenuPressed || null
18
+ this.menuCooldown = false
19
+ this.mobileControls = null
20
+ this.mobileInput = null
21
+ this.editModeCooldown = false
22
+ this.lastEditModeToggle = 0
9
23
 
10
24
  if (config.enableKeyboard !== false) {
11
25
  this.setupKeyboardListeners()
@@ -16,6 +30,14 @@ export class InputHandler {
16
30
  }
17
31
  }
18
32
 
33
+ setMobileControls(mobileControls) {
34
+ this.mobileControls = mobileControls
35
+ }
36
+
37
+ setSmoothTurnSpeed(speed) {
38
+ this.smoothTurnSpeed = speed
39
+ }
40
+
19
41
  setupKeyboardListeners() {
20
42
  if (typeof window === 'undefined') return
21
43
 
@@ -54,23 +76,234 @@ export class InputHandler {
54
76
  right: false,
55
77
  jump: false,
56
78
  shoot: this.mouseDown,
79
+ reload: false,
57
80
  mouseX: this.mouseX,
58
81
  mouseY: this.mouseY
59
82
  }
60
83
  }
61
84
 
85
+ if (this.mobileControls && this.mobileControls.hasInteraction && this.mobileControls.hasInteraction()) {
86
+ const mobileInput = this.mobileControls.getInput()
87
+ if (mobileInput) {
88
+ this.vrYawDelta = mobileInput.yaw
89
+ this.vrPitchDelta = mobileInput.pitch
90
+ this.vrYaw += mobileInput.yaw
91
+ this.vrPitch += mobileInput.pitch
92
+ this.mobileControls.resetLookDelta()
93
+ const zoomValue = mobileInput.zoom
94
+ if (mobileInput.resetZoom) mobileInput.resetZoom()
95
+ return {
96
+ forward: mobileInput.forward,
97
+ backward: mobileInput.backward,
98
+ left: mobileInput.left,
99
+ right: mobileInput.right,
100
+ jump: mobileInput.jump,
101
+ sprint: mobileInput.sprint,
102
+ crouch: mobileInput.crouch,
103
+ shoot: mobileInput.shoot,
104
+ reload: mobileInput.reload,
105
+ yaw: this.vrYaw,
106
+ pitch: this.vrPitch,
107
+ yawDelta: this.vrYawDelta,
108
+ pitchDelta: this.vrPitchDelta,
109
+ zoom: zoomValue,
110
+ mouseX: 0,
111
+ mouseY: 0,
112
+ isMobile: true,
113
+ interact: mobileInput.interact || false,
114
+ weapon: mobileInput.weapon || false,
115
+ analogForward: mobileInput.analogForward || 0,
116
+ analogRight: mobileInput.analogRight || 0
117
+ }
118
+ }
119
+ }
120
+
121
+ const xr = this._getXRInput()
122
+ if (xr) return xr
123
+
124
+ const now = Date.now()
125
+ const pPressed = this.keys.get('p') || false
126
+ if (pPressed && !this.editModeCooldown && now - this.lastEditModeToggle > 200) {
127
+ this.editModeCooldown = true
128
+ this.lastEditModeToggle = now
129
+ } else if (!pPressed) {
130
+ this.editModeCooldown = false
131
+ }
132
+
62
133
  return {
63
134
  forward: this.keys.get('w') || this.keys.get('arrowup') || false,
64
135
  backward: this.keys.get('s') || this.keys.get('arrowdown') || false,
65
136
  left: this.keys.get('a') || this.keys.get('arrowleft') || false,
66
137
  right: this.keys.get('d') || this.keys.get('arrowright') || false,
67
138
  jump: this.keys.get(' ') || false,
139
+ sprint: this.keys.get('shift') || false,
140
+ crouch: this.keys.get('c') || this.keys.get('control') || false,
68
141
  shoot: this.mouseDown,
142
+ reload: this.keys.get('r') || false,
143
+ editToggle: this.editModeCooldown,
69
144
  mouseX: this.mouseX,
70
145
  mouseY: this.mouseY
71
146
  }
72
147
  }
73
148
 
149
+ pulse(handedness, intensity, durationMs) {
150
+ if (!this.renderer?.xr?.isPresenting) return
151
+ const session = this.renderer.xr.getSession()
152
+ if (!session) return
153
+ for (const source of session.inputSources) {
154
+ if (source.handedness === handedness) {
155
+ const gp = source.gamepad
156
+ if (gp?.hapticActuators?.length > 0) {
157
+ gp.hapticActuators[0].pulse(intensity, durationMs)
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ _detectHandGesture(hand) {
164
+ const joints = hand.joints
165
+ if (!joints) return { pinch: false, grab: false, point: false }
166
+
167
+ const thumbTip = joints['thumb-tip']
168
+ const indexTip = joints['index-finger-tip']
169
+ const middleTip = joints['middle-finger-tip']
170
+ const ringTip = joints['ring-finger-tip']
171
+ const pinkyTip = joints['pinky-finger-tip']
172
+ const wrist = joints['wrist']
173
+
174
+ if (!thumbTip || !indexTip || !wrist) return { pinch: false, grab: false, point: false }
175
+
176
+ const pinchDist = Math.sqrt(
177
+ Math.pow(thumbTip.position.x - indexTip.position.x, 2) +
178
+ Math.pow(thumbTip.position.y - indexTip.position.y, 2) +
179
+ Math.pow(thumbTip.position.z - indexTip.position.z, 2)
180
+ )
181
+ const pinch = pinchDist < 0.02
182
+
183
+ let grab = false
184
+ if (middleTip && ringTip && pinkyTip) {
185
+ const palmDist = Math.sqrt(
186
+ Math.pow(wrist.position.x - middleTip.position.x, 2) +
187
+ Math.pow(wrist.position.y - middleTip.position.y, 2) +
188
+ Math.pow(wrist.position.z - middleTip.position.z, 2)
189
+ )
190
+ grab = [middleTip, ringTip, pinkyTip].every(tip => {
191
+ const d = Math.sqrt(
192
+ Math.pow(wrist.position.x - tip.position.x, 2) +
193
+ Math.pow(wrist.position.y - tip.position.y, 2) +
194
+ Math.pow(wrist.position.z - tip.position.z, 2)
195
+ )
196
+ return d < palmDist * 0.7
197
+ })
198
+ }
199
+
200
+ return { pinch, grab, pinchDist }
201
+ }
202
+
203
+ setSnapTurnAngle(angle) {
204
+ this.snapTurnAngle = angle
205
+ }
206
+
207
+ _getXRInput() {
208
+ if (!this.renderer?.xr?.isPresenting) return null
209
+ const session = this.renderer.xr.getSession()
210
+ if (!session) return null
211
+ let forward = false, backward = false, left = false, right = false
212
+ let analogForward = 0, analogRight = 0
213
+ let jump = false, shoot = false, sprint = false, reload = false
214
+ let menu = false
215
+ const DEAD = 0.15, THRESH = 0.5
216
+ let snapTurned = false
217
+ let hasHands = false
218
+ const snapAngleRad = (this.snapTurnAngle * Math.PI) / 180
219
+
220
+ for (const source of session.inputSources) {
221
+ if (source.hand) {
222
+ hasHands = true
223
+ const gestures = this._detectHandGesture(source.hand)
224
+ if (source.handedness === 'left') {
225
+ forward = gestures.grab
226
+ }
227
+ if (source.handedness === 'right') {
228
+ shoot = gestures.pinch
229
+ }
230
+ continue
231
+ }
232
+
233
+ const gp = source.gamepad
234
+ if (!gp) continue
235
+
236
+ if (window.__VR_DEBUG__) {
237
+ console.log(`[VR] ${source.handedness} axes:`, gp.axes.map((a, i) => `${i}:${a?.toFixed(2)}`).join(', '))
238
+ console.log(`[VR] ${source.handedness} btns:`, gp.buttons.map((b, i) => `${i}:${b?.pressed ? '1' : '0'}`).join(', '))
239
+ }
240
+
241
+ const axes = gp.axes
242
+ const btns = gp.buttons
243
+
244
+ const primaryX = axes[0] ?? 0
245
+ const primaryY = axes[1] ?? 0
246
+ const secondaryX = axes.length > 2 ? (axes[2] ?? 0) : 0
247
+ const secondaryY = axes.length > 3 ? (axes[3] ?? 0) : 0
248
+
249
+ if (source.handedness === 'left') {
250
+ if (Math.abs(primaryX) > DEAD) {
251
+ analogRight = primaryX
252
+ if (primaryX > THRESH) right = true
253
+ if (primaryX < -THRESH) left = true
254
+ }
255
+ if (Math.abs(primaryY) > DEAD) {
256
+ analogForward = -primaryY
257
+ if (primaryY < -THRESH) forward = true
258
+ if (primaryY > THRESH) backward = true
259
+ }
260
+
261
+ if (btns[0]?.pressed) jump = true
262
+ if (btns[1]?.pressed || btns[2]?.pressed) sprint = true
263
+ if (btns[2]?.pressed || btns[3]?.pressed || btns[4]?.pressed) reload = true
264
+
265
+ if (btns[4]?.pressed || btns[5]?.pressed || btns[3]?.pressed) {
266
+ if (!this.menuCooldown) {
267
+ menu = true
268
+ this.menuCooldown = true
269
+ if (this.onMenuPressed) this.onMenuPressed()
270
+ }
271
+ } else {
272
+ this.menuCooldown = false
273
+ }
274
+ }
275
+
276
+ if (source.handedness === 'right') {
277
+ const turnX = axes.length > 2 ? secondaryX : primaryX
278
+ const turnY = axes.length > 3 ? secondaryY : 0
279
+
280
+ if (this.smoothTurnSpeed > 0 && Math.abs(turnX) > DEAD) {
281
+ this.vrYaw -= turnX * this.smoothTurnSpeed * 0.016
282
+ snapTurned = true
283
+ } else if (Math.abs(turnX) > DEAD) {
284
+ if (!this.snapCooldown && Math.abs(turnX) > THRESH) {
285
+ this.vrYaw += turnX > 0 ? -snapAngleRad : snapAngleRad
286
+ this.snapCooldown = true
287
+ snapTurned = true
288
+ }
289
+ } else {
290
+ this.snapCooldown = false
291
+ }
292
+
293
+ if (btns[0]?.pressed) shoot = true
294
+ if (btns[2]?.pressed || btns[3]?.pressed || btns[4]?.pressed || btns[5]?.pressed) reload = true
295
+ }
296
+ }
297
+ if (snapTurned) this.pulse('right', 0.3, 50)
298
+ return {
299
+ forward, backward, left, right,
300
+ analogForward, analogRight,
301
+ jump, sprint, shoot, reload, menu,
302
+ yaw: this.vrYaw, pitch: this.vrPitch,
303
+ mouseX: 0, mouseY: 0, hasHands
304
+ }
305
+ }
306
+
74
307
  onInput(callback) {
75
308
  this.callbacks.push(callback)
76
309
  }
@@ -0,0 +1,207 @@
1
+ export class JitterBuffer {
2
+ constructor(config = {}) {
3
+ this.maxSize = config.maxSize || 32
4
+ this.maxAge = config.maxAge || 200
5
+ this.minBufferSize = config.minBufferSize || 2
6
+ this.targetDelay = config.targetDelay || 50
7
+
8
+ this.buffer = []
9
+ this.lastProcessTime = 0
10
+ this.lastServerTime = 0
11
+ this.lastTick = 0
12
+ this.rtt = config.initialRtt || 50
13
+ this.rttVariance = 0
14
+ this.clockDelta = 0
15
+ this.clockDeltaVariance = 0
16
+ this.lastClientTime = 0
17
+ }
18
+
19
+ addSnapshot(snapshot) {
20
+ const now = Date.now()
21
+ const clientTime = now
22
+ const serverTime = snapshot.timestamp || now
23
+
24
+ if (this.lastServerTime > 0) {
25
+ const serverDelta = serverTime - this.lastServerTime
26
+ const clientDelta = clientTime - this.lastClientTime
27
+
28
+ if (serverDelta > 0 && clientDelta > 0) {
29
+ const instantClockDelta = clientDelta - serverDelta
30
+ this.clockDeltaVariance = this.clockDeltaVariance * 0.9 + Math.abs(instantClockDelta - this.clockDelta) * 0.1
31
+ this.clockDelta = this.clockDelta * 0.9 + instantClockDelta * 0.1
32
+ }
33
+ }
34
+
35
+ this.lastServerTime = serverTime
36
+ this.lastClientTime = clientTime
37
+
38
+ this.buffer.push({
39
+ snapshot,
40
+ clientTime,
41
+ serverTime,
42
+ tick: snapshot.tick || 0
43
+ })
44
+
45
+ this.buffer.sort((a, b) => a.tick - b.tick)
46
+
47
+ while (this.buffer.length > this.maxSize) {
48
+ this.buffer.shift()
49
+ }
50
+
51
+ this._pruneOld(now)
52
+ }
53
+
54
+ _pruneOld(now) {
55
+ const cutoff = now - this.maxAge
56
+ while (this.buffer.length > 0 && this.buffer[0].clientTime < cutoff) {
57
+ this.buffer.shift()
58
+ }
59
+ }
60
+
61
+ getSnapshotToRender(now = Date.now()) {
62
+ if (this.buffer.length < this.minBufferSize) {
63
+ if (this.buffer.length === 0) return null
64
+ return this.buffer[this.buffer.length - 1].snapshot
65
+ }
66
+
67
+ const renderTime = now - this.targetDelay
68
+
69
+ let newest = this.buffer[this.buffer.length - 1]
70
+ let oldest = this.buffer[0]
71
+
72
+ if (renderTime >= newest.clientTime) {
73
+ return newest.snapshot
74
+ }
75
+
76
+ if (renderTime <= oldest.clientTime) {
77
+ return oldest.snapshot
78
+ }
79
+
80
+ for (let i = 0; i < this.buffer.length - 1; i++) {
81
+ const curr = this.buffer[i]
82
+ const next = this.buffer[i + 1]
83
+
84
+ if (renderTime >= curr.clientTime && renderTime <= next.clientTime) {
85
+ const range = next.clientTime - curr.clientTime
86
+ if (range === 0) return curr.snapshot
87
+
88
+ const alpha = (renderTime - curr.clientTime) / range
89
+ return this._interpolateSnapshots(curr.snapshot, next.snapshot, alpha)
90
+ }
91
+ }
92
+
93
+ return newest.snapshot
94
+ }
95
+
96
+ _interpolateSnapshots(older, newer, alpha) {
97
+ const interpolated = {
98
+ tick: Math.round(older.tick + (newer.tick - older.tick) * alpha),
99
+ timestamp: older.timestamp + (newer.timestamp - older.timestamp) * alpha,
100
+ players: [],
101
+ entities: []
102
+ }
103
+
104
+ const olderPlayers = new Map()
105
+ for (const p of older.players || []) {
106
+ olderPlayers.set(p.id, p)
107
+ }
108
+
109
+ for (const np of newer.players || []) {
110
+ const op = olderPlayers.get(np.id)
111
+ if (op) {
112
+ interpolated.players.push(this._interpolatePlayer(op, np, alpha))
113
+ } else {
114
+ interpolated.players.push({ ...np })
115
+ }
116
+ }
117
+
118
+ const olderEntities = new Map()
119
+ for (const e of older.entities || []) {
120
+ olderEntities.set(e.id, e)
121
+ }
122
+
123
+ for (const ne of newer.entities || []) {
124
+ const oe = olderEntities.get(ne.id)
125
+ if (oe) {
126
+ interpolated.entities.push(this._interpolateEntity(oe, ne, alpha))
127
+ } else {
128
+ interpolated.entities.push({ ...ne })
129
+ }
130
+ }
131
+
132
+ return interpolated
133
+ }
134
+
135
+ _interpolatePlayer(older, newer, alpha) {
136
+ return {
137
+ id: newer.id,
138
+ position: [
139
+ this._lerp(older.position[0], newer.position[0], alpha),
140
+ this._lerp(older.position[1], newer.position[1], alpha),
141
+ this._lerp(older.position[2], newer.position[2], alpha)
142
+ ],
143
+ rotation: newer.rotation,
144
+ velocity: [
145
+ this._lerp(older.velocity?.[0] || 0, newer.velocity?.[0] || 0, alpha),
146
+ this._lerp(older.velocity?.[1] || 0, newer.velocity?.[1] || 0, alpha),
147
+ this._lerp(older.velocity?.[2] || 0, newer.velocity?.[2] || 0, alpha)
148
+ ],
149
+ onGround: newer.onGround,
150
+ health: this._lerp(older.health || 100, newer.health || 100, alpha),
151
+ inputSequence: newer.inputSequence,
152
+ crouch: newer.crouch,
153
+ lookPitch: this._lerp(older.lookPitch || 0, newer.lookPitch || 0, alpha),
154
+ lookYaw: this._lerp(older.lookYaw || 0, newer.lookYaw || 0, alpha)
155
+ }
156
+ }
157
+
158
+ _interpolateEntity(older, newer, alpha) {
159
+ return {
160
+ id: newer.id,
161
+ model: newer.model,
162
+ position: [
163
+ this._lerp(older.position[0], newer.position[0], alpha),
164
+ this._lerp(older.position[1], newer.position[1], alpha),
165
+ this._lerp(older.position[2], newer.position[2], alpha)
166
+ ],
167
+ rotation: [
168
+ this._lerp(older.rotation[0], newer.rotation[0], alpha),
169
+ this._lerp(older.rotation[1], newer.rotation[1], alpha),
170
+ this._lerp(older.rotation[2], newer.rotation[2], alpha),
171
+ this._lerp(older.rotation[3], newer.rotation[3], alpha)
172
+ ],
173
+ bodyType: newer.bodyType,
174
+ custom: newer.custom
175
+ }
176
+ }
177
+
178
+ _lerp(a, b, t) {
179
+ return a + (b - a) * t
180
+ }
181
+
182
+ updateRTT(pingTime, pongTime) {
183
+ const instantRtt = pongTime - pingTime
184
+ this.rttVariance = this.rttVariance * 0.75 + Math.abs(instantRtt - this.rtt) * 0.25
185
+ this.rtt = this.rtt * 0.875 + instantRtt * 0.125
186
+
187
+ this.targetDelay = Math.min(100, Math.max(20, this.rtt / 2 + this.rttVariance))
188
+ }
189
+
190
+ getBufferHealth() {
191
+ return this.buffer.length
192
+ }
193
+
194
+ getRTT() {
195
+ return this.rtt
196
+ }
197
+
198
+ getClockDelta() {
199
+ return this.clockDelta
200
+ }
201
+
202
+ clear() {
203
+ this.buffer = []
204
+ this.lastServerTime = 0
205
+ this.lastClientTime = 0
206
+ }
207
+ }
@@ -0,0 +1,125 @@
1
+ export class KalmanFilter3D {
2
+ constructor(config = {}) {
3
+ this.processNoise = config.processNoise || 0.1
4
+ this.measurementNoise = config.measurementNoise || 0.5
5
+ this.uncertainty = config.uncertainty || 1.0
6
+
7
+ this.x = [0, 0, 0]
8
+ this.v = [0, 0, 0]
9
+
10
+ this.P = [
11
+ [this.uncertainty, 0, 0],
12
+ [0, this.uncertainty, 0],
13
+ [0, 0, this.uncertainty]
14
+ ]
15
+
16
+ this.initialized = false
17
+ }
18
+
19
+ init(position, velocity = [0, 0, 0]) {
20
+ this.x = [...position]
21
+ this.v = [...velocity]
22
+ this.initialized = true
23
+ }
24
+
25
+ predict(dt) {
26
+ if (!this.initialized) return { position: this.x, velocity: this.v }
27
+
28
+ for (let i = 0; i < 3; i++) {
29
+ this.x[i] += this.v[i] * dt
30
+ }
31
+
32
+ const q = this.processNoise * dt * dt
33
+ for (let i = 0; i < 3; i++) {
34
+ this.P[i][i] += q
35
+ }
36
+
37
+ return { position: [...this.x], velocity: [...this.v] }
38
+ }
39
+
40
+ update(measuredPosition, measuredVelocity = null) {
41
+ if (!this.initialized) {
42
+ this.init(measuredPosition, measuredVelocity || [0, 0, 0])
43
+ return { position: [...this.x], velocity: [...this.v] }
44
+ }
45
+
46
+ const R = this.measurementNoise
47
+
48
+ for (let i = 0; i < 3; i++) {
49
+ const P = this.P[i][i]
50
+ const K = P / (P + R)
51
+
52
+ this.x[i] += K * (measuredPosition[i] - this.x[i])
53
+
54
+ if (measuredVelocity) {
55
+ this.v[i] = measuredVelocity[i]
56
+ }
57
+
58
+ this.P[i][i] = (1 - K) * P
59
+ }
60
+
61
+ return { position: [...this.x], velocity: [...this.v] }
62
+ }
63
+
64
+ getState() {
65
+ return {
66
+ position: [...this.x],
67
+ velocity: [...this.v]
68
+ }
69
+ }
70
+
71
+ setPosition(pos) {
72
+ this.x = [...pos]
73
+ }
74
+
75
+ setVelocity(vel) {
76
+ this.v = [...vel]
77
+ }
78
+
79
+ reset(position = [0, 0, 0]) {
80
+ this.x = [...position]
81
+ this.v = [0, 0, 0]
82
+ this.P = [
83
+ [this.uncertainty, 0, 0],
84
+ [0, this.uncertainty, 0],
85
+ [0, 0, this.uncertainty]
86
+ ]
87
+ this.initialized = false
88
+ }
89
+ }
90
+
91
+ export class SmoothStateTracker {
92
+ constructor(config = {}) {
93
+ this.filters = new Map()
94
+ this.maxAge = config.maxAge || 5000
95
+ this.defaultConfig = config.filterConfig || {}
96
+ }
97
+
98
+ getFilter(id) {
99
+ let filter = this.filters.get(id)
100
+ if (!filter) {
101
+ filter = new KalmanFilter3D(this.defaultConfig)
102
+ this.filters.set(id, filter)
103
+ }
104
+ return filter
105
+ }
106
+
107
+ update(id, position, velocity, dt) {
108
+ const filter = this.getFilter(id)
109
+ filter.predict(dt)
110
+ return filter.update(position, velocity)
111
+ }
112
+
113
+ predict(id, dt) {
114
+ const filter = this.getFilter(id)
115
+ return filter.predict(dt)
116
+ }
117
+
118
+ remove(id) {
119
+ this.filters.delete(id)
120
+ }
121
+
122
+ clear() {
123
+ this.filters.clear()
124
+ }
125
+ }
@@ -0,0 +1,101 @@
1
+ import { PredictionEngine } from './PredictionEngine.js'
2
+ import { SmoothInterpolation } from './SmoothInterpolation.js'
3
+ import { MSG } from '../protocol/MessageTypes.js'
4
+
5
+ export class MessageHandler {
6
+ constructor(config = {}) {
7
+ this._config = config
8
+ this._predEngine = null
9
+ this._smoothInterp = null
10
+ this._playerId = null
11
+ this._currentTick = 0
12
+ this._callbacks = config.callbacks || {}
13
+ }
14
+
15
+ handleMessage(type, payload, snapProc) {
16
+ if (type === MSG.HANDSHAKE_ACK) {
17
+ return this._handleHandshake(payload)
18
+ } else if (type === MSG.RECONNECT_ACK) {
19
+ return this._handleReconnect(payload, snapProc)
20
+ } else if (type === MSG.STATE_RECOVERY) {
21
+ return payload.snapshot
22
+ } else if (type === MSG.DISCONNECT_REASON) {
23
+ if (payload.code === 4) return { invalidate: true }
24
+ } else if (type === MSG.SNAPSHOT || type === MSG.STATE_CORRECTION) {
25
+ return payload
26
+ } else if (type === MSG.PLAYER_LEAVE) {
27
+ snapProc?.removePlayer(payload.playerId)
28
+ this._callbacks.onPlayerLeft?.(payload.playerId)
29
+ } else if (type === MSG.WORLD_DEF) {
30
+ if (payload.movement && this._predEngine) this._predEngine.setMovement(payload.movement)
31
+ if (payload.gravity && this._predEngine) this._predEngine.setGravity(payload.gravity)
32
+ this._callbacks.onWorldDef?.(payload)
33
+ } else if (type === MSG.APP_EVENT) {
34
+ this._callbacks.onAppEvent?.(payload)
35
+ } else if (type === MSG.HOT_RELOAD || type === MSG.APP_MODULE || type === MSG.ASSET_UPDATE) {
36
+ const cb = { [MSG.HOT_RELOAD]: 'onHotReload', [MSG.APP_MODULE]: 'onAppModule', [MSG.ASSET_UPDATE]: 'onAssetUpdate' }[type]
37
+ this._callbacks[cb]?.(payload)
38
+ } else if (type === MSG.HEARTBEAT_ACK) {
39
+ this._handleHeartbeat(payload)
40
+ }
41
+ }
42
+
43
+ _handleHandshake(payload) {
44
+ this._playerId = payload.playerId
45
+ this._currentTick = payload.tick
46
+ this._predEngine = new PredictionEngine(this._config.tickRate || 128)
47
+ this._predEngine.init(this._playerId)
48
+ if (this._config.smoothInterpolation !== false) {
49
+ this._smoothInterp = new SmoothInterpolation({ predictionEnabled: this._config.predictionEnabled !== false })
50
+ this._smoothInterp.setLocalPlayer(this._playerId)
51
+ }
52
+ return { sessionToken: payload.sessionToken }
53
+ }
54
+
55
+ _handleReconnect(payload, snapProc) {
56
+ const oldPlayerId = this._playerId
57
+ this._playerId = payload.playerId
58
+ this._currentTick = payload.tick
59
+ if (oldPlayerId && oldPlayerId !== this._playerId) {
60
+ snapProc?.removePlayer(oldPlayerId)
61
+ if (this._smoothInterp) this._smoothInterp.removePlayer(oldPlayerId)
62
+ this._callbacks.onPlayerLeft?.(oldPlayerId)
63
+ }
64
+ if (!this._predEngine) {
65
+ this._predEngine = new PredictionEngine(this._config.tickRate || 128)
66
+ this._predEngine.init(this._playerId)
67
+ }
68
+ if (this._config.smoothInterpolation !== false && !this._smoothInterp) {
69
+ this._smoothInterp = new SmoothInterpolation({ predictionEnabled: this._config.predictionEnabled !== false })
70
+ this._smoothInterp.setLocalPlayer(this._playerId)
71
+ }
72
+ return { sessionToken: payload.sessionToken }
73
+ }
74
+
75
+ _handleHeartbeat(payload) {
76
+ if (this._smoothInterp) {
77
+ this._smoothInterp.updateRTT(payload.pingTime || 0, Date.now())
78
+ }
79
+ }
80
+
81
+ getPlayerId() { return this._playerId }
82
+ getCurrentTick() { return this._currentTick }
83
+ setCurrentTick(tick) { this._currentTick = tick }
84
+ getPredEngine() { return this._predEngine }
85
+ getSmoothInterp() { return this._smoothInterp }
86
+
87
+ getDisplayState(tick) {
88
+ if (this._smoothInterp) {
89
+ return this._smoothInterp.getDisplayState()
90
+ }
91
+ return { players: [], entities: [] }
92
+ }
93
+
94
+ getRTT() {
95
+ return this._smoothInterp?.getRTT() || 0
96
+ }
97
+
98
+ getBufferHealth() {
99
+ return this._smoothInterp?.getBufferHealth() || 0
100
+ }
101
+ }