spoint 0.1.60 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/app.js CHANGED
@@ -1033,20 +1033,10 @@ const client = new PhysicsNetworkClient({
1033
1033
  predictionEnabled: true,
1034
1034
  smoothInterpolation: true,
1035
1035
  onStateUpdate: (state) => {
1036
- const smoothState = client.getSmoothState()
1037
- for (const p of smoothState.players) {
1036
+ for (const p of state.players) {
1038
1037
  if (!playerMeshes.has(p.id)) createPlayerVRM(p.id)
1039
- const mesh = playerMeshes.get(p.id)
1040
- const feetOff = mesh?.userData?.feetOffset ?? 1.3
1041
- const tx = p.position[0], ty = p.position[1] - feetOff, tz = p.position[2]
1042
- const existingTarget = playerTargets.get(p.id)
1043
- if (existingTarget) { existingTarget.x = tx; existingTarget.y = ty; existingTarget.z = tz }
1044
- else playerTargets.set(p.id, { x: tx, y: ty, z: tz })
1045
- playerStates.set(p.id, p)
1046
- const dx = tx - mesh.position.x, dy = ty - mesh.position.y, dz = tz - mesh.position.z
1047
- if (!mesh.userData.initialized || dx * dx + dy * dy + dz * dz > 100) { mesh.position.set(tx, ty, tz); mesh.userData.initialized = true }
1048
1038
  }
1049
- for (const e of smoothState.entities) {
1039
+ for (const e of state.entities) {
1050
1040
  const mesh = entityMeshes.get(e.id)
1051
1041
  if (mesh && e.position) {
1052
1042
  const et = entityTargets.get(e.id)
@@ -1057,11 +1047,10 @@ const client = new PhysicsNetworkClient({
1057
1047
  }
1058
1048
  if (!entityMeshes.has(e.id)) loadEntityModel(e.id, e)
1059
1049
  }
1060
- rebuildEntityHierarchy(smoothState.entities)
1061
1050
  latestState = state
1062
1051
  if (!firstSnapshotReceived) {
1063
1052
  firstSnapshotReceived = true
1064
- for (const e of smoothState.entities) {
1053
+ for (const e of state.entities) {
1065
1054
  if (e.model && !entityMeshes.has(e.id)) firstSnapshotEntityPending.add(e.id)
1066
1055
  }
1067
1056
  checkAllLoaded()
@@ -1467,7 +1456,27 @@ function animate(timestamp) {
1467
1456
  const frameDt = smoothDt
1468
1457
  fpsFrames++
1469
1458
  if (now - fpsLast >= 1000) { fpsDisplay = fpsFrames; fpsFrames = 0; fpsLast = now }
1470
- const lerpFactor = 1.0 - Math.exp(-16.0 * frameDt)
1459
+ const rttMs = client.getRTT?.() || 0
1460
+ const lerpConstant = rttMs > 100 ? 24.0 : 16.0
1461
+ const lerpFactor = 1.0 - Math.exp(-lerpConstant * frameDt)
1462
+ const smoothState = client.getSmoothState()
1463
+ for (const p of smoothState.players) {
1464
+ if (!playerMeshes.has(p.id)) continue
1465
+ const mesh = playerMeshes.get(p.id)
1466
+ const feetOff = mesh?.userData?.feetOffset ?? 1.3
1467
+ const tx = p.position[0], ty = p.position[1] - feetOff, tz = p.position[2]
1468
+ const existingTarget = playerTargets.get(p.id)
1469
+ if (existingTarget) { existingTarget.x = tx; existingTarget.y = ty; existingTarget.z = tz }
1470
+ else playerTargets.set(p.id, { x: tx, y: ty, z: tz })
1471
+ playerStates.set(p.id, p)
1472
+ if (!mesh.userData.initialized) { mesh.position.set(tx, ty, tz); mesh.userData.initialized = true }
1473
+ }
1474
+ for (const e of smoothState.entities) {
1475
+ const mesh = entityMeshes.get(e.id)
1476
+ if (mesh && e.position) mesh.position.set(e.position[0], e.position[1], e.position[2])
1477
+ if (mesh && e.rotation) mesh.quaternion.set(e.rotation[0], e.rotation[1], e.rotation[2], e.rotation[3])
1478
+ }
1479
+ if (smoothState.entities.length > 0) rebuildEntityHierarchy(smoothState.entities)
1471
1480
  playerTargets.forEach((target, id) => {
1472
1481
  const mesh = playerMeshes.get(id)
1473
1482
  if (!mesh) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -34,7 +34,7 @@ export class JitterBuffer {
34
34
 
35
35
  while (this.buffer.length > this.maxSize) this.buffer.shift()
36
36
 
37
- const maxAge = Math.max(300, this.rtt + this.jitter * 3)
37
+ const maxAge = Math.max(400, this.rtt + this.jitter * 3 + 150)
38
38
  const cutoff = now - maxAge
39
39
  while (this.buffer.length > 0 && this.buffer[0].clientTime < cutoff) this.buffer.shift()
40
40
  }
@@ -111,8 +111,9 @@ export class JitterBuffer {
111
111
  updateRTT(pingTime, pongTime) {
112
112
  const instant = pongTime - pingTime
113
113
  this.rttVariance = this.rttVariance * 0.75 + Math.abs(instant - this.rtt) * 0.25
114
- this.rtt = this.rtt * 0.875 + instant * 0.125
115
- this.targetDelay = this.baseDelay + this.rtt * 0.5 + this.jitter * 2
114
+ const alpha = instant > this.rtt ? 0.5 : 0.1
115
+ this.rtt = this.rtt * (1 - alpha) + instant * alpha
116
+ this.targetDelay = Math.min(250, this.baseDelay + this.rtt * 0.5 + this.jitter * 2)
116
117
  }
117
118
 
118
119
  getBufferHealth() { return this.buffer.length }
@@ -56,15 +56,14 @@ export class MessageHandler {
56
56
  const oldPlayerId = this._playerId
57
57
  this._playerId = payload.playerId
58
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)
59
+ snapProc?.clear()
60
+ if (this._smoothInterp) {
61
+ this._smoothInterp.reset()
62
+ this._smoothInterp.setLocalPlayer(this._playerId)
67
63
  }
64
+ if (oldPlayerId) this._callbacks.onPlayerLeft?.(oldPlayerId)
65
+ this._predEngine = new PredictionEngine(this._config.tickRate || 128)
66
+ this._predEngine.init(this._playerId)
68
67
  if (this._config.smoothInterpolation !== false && !this._smoothInterp) {
69
68
  this._smoothInterp = new SmoothInterpolation({ predictionEnabled: this._config.predictionEnabled !== false })
70
69
  this._smoothInterp.setLocalPlayer(this._playerId)
@@ -2,35 +2,22 @@ export class ReconciliationEngine {
2
2
  constructor(config = {}) {
3
3
  this.correctionThreshold = config.correctionThreshold || 0.01
4
4
  this.correctionSpeed = config.correctionSpeed || 0.5
5
- this.lastReconcileTime = 0
6
- this.reconcileInterval = config.reconcileInterval || 100
7
5
  }
8
6
 
9
7
  reconcile(serverState, localState, tick) {
10
- const now = Date.now()
11
- if (now - this.lastReconcileTime < this.reconcileInterval) {
12
- return { needsCorrection: false, correction: null }
13
- }
14
-
15
- this.lastReconcileTime = now
16
-
17
8
  const divergence = this.calculateDivergence(serverState, localState)
18
-
19
9
  if (divergence < this.correctionThreshold) {
20
10
  return { needsCorrection: false, divergence }
21
11
  }
22
-
23
12
  const correction = this.generateCorrection(serverState, localState)
24
13
  return { needsCorrection: true, correction, divergence }
25
14
  }
26
15
 
27
16
  calculateDivergence(serverState, localState) {
28
17
  if (!serverState || !localState) return 0
29
-
30
18
  const dx = serverState.position[0] - localState.position[0]
31
19
  const dy = serverState.position[1] - localState.position[1]
32
20
  const dz = serverState.position[2] - localState.position[2]
33
-
34
21
  return Math.sqrt(dx * dx + dy * dy + dz * dz)
35
22
  }
36
23
 
@@ -12,7 +12,6 @@ export class SmoothInterpolation {
12
12
  this.entityKalmanConfig = config.entityKalman || {
13
13
  positionQ: 2.0, velocityQ: 4.0, positionR: 0.01, velocityR: 0.5
14
14
  }
15
-
16
15
  this.localPlayerId = null
17
16
  this.predictionEnabled = config.predictionEnabled !== false
18
17
  this._lastDisplayTime = 0