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
@@ -13,7 +13,8 @@ export class SessionStore {
13
13
  token,
14
14
  playerId,
15
15
  state: state ? { ...state } : {},
16
- createdAt: Date.now()
16
+ createdAt: Date.now(),
17
+ lastTouched: Date.now()
17
18
  }
18
19
  this.sessions.set(token, session)
19
20
  this._setupExpire(token)
@@ -24,17 +25,20 @@ export class SessionStore {
24
25
  const session = this.sessions.get(token)
25
26
  if (!session) return false
26
27
  if (data.state) Object.assign(session.state, data.state)
27
- session.lastUpdated = Date.now()
28
+ session.lastTouched = Date.now()
29
+ this._refreshExpire(token)
28
30
  return true
29
31
  }
30
32
 
31
33
  get(token) {
32
34
  const session = this.sessions.get(token)
33
35
  if (!session) return null
34
- if (Date.now() - session.createdAt > this.ttl) {
36
+ if (Date.now() - session.lastTouched > this.ttl) {
35
37
  this.destroy(token)
36
38
  return null
37
39
  }
40
+ session.lastTouched = Date.now()
41
+ this._refreshExpire(token)
38
42
  return session
39
43
  }
40
44
 
@@ -46,6 +50,12 @@ export class SessionStore {
46
50
  this.timers.set(token, timer)
47
51
  }
48
52
 
53
+ _refreshExpire(token) {
54
+ const old = this.timers.get(token)
55
+ if (old) clearTimeout(old)
56
+ this._setupExpire(token)
57
+ }
58
+
49
59
  destroy(token) {
50
60
  const timer = this.timers.get(token)
51
61
  if (timer) clearTimeout(timer)
@@ -1,8 +1,6 @@
1
1
  export { InputHandler } from './client/InputHandler.js'
2
2
  export { PhysicsNetworkClient } from './client/PhysicsNetworkClient.js'
3
- export { PredictionEngine } from './client/PredictionEngine.js'
4
- export { ReconciliationEngine } from './client/ReconciliationEngine.js'
5
-
6
- export { MSG, msgName, DISCONNECT_REASONS, CONNECTION_QUALITY, UNRELIABLE_MSGS, isUnreliable } from './protocol/MessageTypes.js'
7
- export { Codec } from './protocol/Codec.js'
8
- export { SequenceTracker } from './protocol/SequenceTracker.js'
3
+ export { MSG } from './protocol/MessageTypes.js'
4
+ export { KalmanFilter3D, SmoothStateTracker } from './client/KalmanFilter.js'
5
+ export { JitterBuffer } from './client/JitterBuffer.js'
6
+ export { SmoothInterpolation } from './client/SmoothInterpolation.js'
@@ -1,7 +1,9 @@
1
1
  export class EventLog {
2
2
  constructor(config = {}) {
3
- this._log = []
4
- this._maxSize = config.maxSize || 100000
3
+ this._maxSize = config.maxSize || 1000
4
+ this._buf = new Array(this._maxSize)
5
+ this._head = 0
6
+ this._count = 0
5
7
  this._nextId = 1
6
8
  this._recording = true
7
9
  }
@@ -16,13 +18,19 @@ export class EventLog {
16
18
  data,
17
19
  meta: { actor: meta.actor || null, reason: meta.reason || null, context: meta.context || null, sourceApp: meta.sourceApp || null, sourceEntity: meta.sourceEntity || null, causalEventId: meta.causalEventId || null, ...meta }
18
20
  }
19
- this._log.push(event)
20
- if (this._log.length > this._maxSize) this._log.shift()
21
+ this._buf[this._head] = event
22
+ this._head = (this._head + 1) % this._maxSize
23
+ if (this._count < this._maxSize) this._count++
21
24
  return event
22
25
  }
23
26
 
27
+ _toArray() {
28
+ if (this._count < this._maxSize) return this._buf.slice(0, this._count)
29
+ return [...this._buf.slice(this._head), ...this._buf.slice(0, this._head)]
30
+ }
31
+
24
32
  query(filter = {}) {
25
- return this._log.filter(e => {
33
+ return this._toArray().filter(e => {
26
34
  if (filter.type && e.type !== filter.type) return false
27
35
  if (filter.tick !== undefined && e.tick !== filter.tick) return false
28
36
  if (filter.tickRange && (e.tick < filter.tickRange[0] || e.tick > filter.tickRange[1])) return false
@@ -34,30 +42,36 @@ export class EventLog {
34
42
  }
35
43
 
36
44
  getRange(startTick, endTick) {
37
- return this._log.filter(e => e.tick >= startTick && e.tick <= endTick)
45
+ return this._toArray().filter(e => e.tick >= startTick && e.tick <= endTick)
38
46
  }
39
47
 
40
- get size() { return this._log.length }
41
- get lastTick() { return this._log.length > 0 ? this._log[this._log.length - 1].tick : 0 }
48
+ get size() { return this._count }
49
+ get lastTick() {
50
+ if (this._count === 0) return 0
51
+ const idx = (this._head - 1 + this._maxSize) % this._maxSize
52
+ return this._buf[idx].tick
53
+ }
42
54
 
43
55
  pause() { this._recording = false }
44
56
  resume() { this._recording = true }
45
- clear() { this._log = []; this._nextId = 1 }
57
+ clear() { this._buf = new Array(this._maxSize); this._head = 0; this._count = 0; this._nextId = 1 }
46
58
 
47
- serialize() { return JSON.stringify(this._log) }
59
+ serialize() { return JSON.stringify(this._toArray()) }
48
60
 
49
61
  static deserialize(json) {
50
- const log = new EventLog()
51
- log._log = JSON.parse(json)
52
- log._nextId = log._log.length > 0 ? log._log[log._log.length - 1].id + 1 : 1
62
+ const arr = JSON.parse(json)
63
+ const log = new EventLog({ maxSize: Math.max(arr.length, 1000) })
64
+ for (const e of arr) log._buf[log._head++] = e
65
+ log._count = arr.length
66
+ log._head = log._head % log._maxSize
67
+ log._nextId = arr.length > 0 ? arr[arr.length - 1].id + 1 : 1
53
68
  return log
54
69
  }
55
70
 
56
71
  replay(runtime, options = {}) {
57
- const speed = options.speed || 1
58
72
  const startTick = options.startTick || 0
59
73
  const endTick = options.endTick || Infinity
60
- const events = this._log.filter(e => e.tick >= startTick && e.tick <= endTick)
74
+ const events = this._toArray().filter(e => e.tick >= startTick && e.tick <= endTick)
61
75
  const result = { eventsReplayed: 0, errors: [] }
62
76
  for (const event of events) {
63
77
  try {
@@ -6,37 +6,37 @@ export class LagCompensator {
6
6
 
7
7
  recordPlayerPosition(playerId, position, rotation, velocity, tick) {
8
8
  if (!this.playerHistory.has(playerId)) {
9
- this.playerHistory.set(playerId, [])
9
+ this.playerHistory.set(playerId, { buf: new Array(128), head: 0, len: 0 })
10
10
  }
11
11
 
12
- const history = this.playerHistory.get(playerId)
13
- history.push({
14
- tick,
15
- timestamp: Date.now(),
16
- position: [...position],
17
- rotation: [...rotation],
18
- velocity: [...velocity]
19
- })
12
+ const ring = this.playerHistory.get(playerId)
13
+ const idx = (ring.head + ring.len) % 128
14
+ if (!ring.buf[idx]) ring.buf[idx] = { tick: 0, timestamp: 0, position: [0,0,0], rotation: [0,0,0,1], velocity: [0,0,0] }
15
+ const entry = ring.buf[idx]
16
+ entry.tick = tick; entry.timestamp = Date.now()
17
+ entry.position[0] = position[0]; entry.position[1] = position[1]; entry.position[2] = position[2]
18
+ entry.rotation[0] = rotation[0]; entry.rotation[1] = rotation[1]; entry.rotation[2] = rotation[2]; entry.rotation[3] = rotation[3]
19
+ entry.velocity[0] = velocity[0]; entry.velocity[1] = velocity[1]; entry.velocity[2] = velocity[2]
20
+ if (ring.len < 128) ring.len++
21
+ else ring.head = (ring.head + 1) % 128
20
22
 
21
23
  const cutoff = Date.now() - this.historyWindow
22
- while (history.length > 0 && history[0].timestamp < cutoff) {
23
- history.shift()
24
+ while (ring.len > 0 && ring.buf[ring.head].timestamp < cutoff) {
25
+ ring.head = (ring.head + 1) % 128; ring.len--
24
26
  }
25
27
  }
26
28
 
27
29
  getPlayerStateAtTime(playerId, millisAgo) {
28
- const history = this.playerHistory.get(playerId)
29
- if (!history || history.length === 0) return null
30
+ const ring = this.playerHistory.get(playerId)
31
+ if (!ring || ring.len === 0) return null
30
32
 
31
33
  const targetTime = Date.now() - millisAgo
32
34
  let best = null
33
35
 
34
- for (const state of history) {
35
- if (state.timestamp <= targetTime) {
36
- best = state
37
- } else {
38
- break
39
- }
36
+ for (let i = 0; i < ring.len; i++) {
37
+ const entry = ring.buf[(ring.head + i) % 128]
38
+ if (entry.timestamp <= targetTime) best = entry
39
+ else break
40
40
  }
41
41
 
42
42
  return best
@@ -56,10 +56,10 @@ export class LagCompensator {
56
56
  }
57
57
 
58
58
  detectTeleport(playerId, newPosition, threshold = 50) {
59
- const history = this.playerHistory.get(playerId)
60
- if (!history || history.length < 2) return false
59
+ const ring = this.playerHistory.get(playerId)
60
+ if (!ring || ring.len < 2) return false
61
61
 
62
- const lastPos = history[history.length - 1].position
62
+ const lastPos = ring.buf[(ring.head + ring.len - 1) % 128].position
63
63
  const dist = Math.sqrt((newPosition[0] - lastPos[0])**2 + (newPosition[1] - lastPos[1])**2 + (newPosition[2] - lastPos[2])**2)
64
64
 
65
65
  return dist > threshold
@@ -70,9 +70,8 @@ export class LagCompensator {
70
70
  }
71
71
 
72
72
  getStats() {
73
- return {
74
- trackedPlayers: this.playerHistory.size,
75
- totalSamples: Array.from(this.playerHistory.values()).reduce((sum, h) => sum + h.length, 0)
76
- }
73
+ let total = 0
74
+ for (const ring of this.playerHistory.values()) total += ring.len
75
+ return { trackedPlayers: this.playerHistory.size, totalSamples: total }
77
76
  }
78
77
  }
@@ -50,7 +50,10 @@ export class NetworkState {
50
50
  velocity: p.velocity,
51
51
  onGround: p.onGround,
52
52
  health: p.health,
53
- inputSequence: p.inputSequence
53
+ inputSequence: p.inputSequence,
54
+ crouch: p.crouch || 0,
55
+ lookPitch: p.lookPitch || 0,
56
+ lookYaw: p.lookYaw || 0
54
57
  }))
55
58
  }
56
59
  }
@@ -5,21 +5,26 @@ export class PhysicsIntegration {
5
5
  gravity: config.gravity || [0, -9.81, 0],
6
6
  capsuleRadius: config.capsuleRadius || 0.4,
7
7
  capsuleHalfHeight: config.capsuleHalfHeight || 0.9,
8
- playerMass: config.playerMass || 160,
8
+ crouchHalfHeight: config.crouchHalfHeight || 0.45,
9
+ playerMass: config.playerMass || 120,
9
10
  ...config
10
11
  }
11
12
  this.playerBodies = new Map()
13
+ this._crouchStates = new Map()
12
14
  }
13
15
 
14
16
  setPhysicsWorld(world) {
15
17
  this.physicsWorld = world
16
18
  }
17
19
 
18
- addPlayerCollider(playerId, radius = 0.4) {
19
- if (!this.physicsWorld) {
20
- this.playerBodies.set(playerId, { id: playerId, charId: null, onGround: true })
21
- return
22
- }
20
+ addPlayerCollider(playerId, radius = 0.4) {
21
+ if (this.playerBodies.has(playerId)) {
22
+ this.removePlayerCollider(playerId)
23
+ }
24
+ if (!this.physicsWorld) {
25
+ this.playerBodies.set(playerId, { id: playerId, charId: null, onGround: true })
26
+ return
27
+ }
23
28
  const charId = this.physicsWorld.addPlayerCharacter(
24
29
  radius,
25
30
  this.config.capsuleHalfHeight,
@@ -129,4 +134,13 @@ export class PhysicsIntegration {
129
134
  if (distance > 2.0) return { valid: false, reason: 'move_too_far', distance }
130
135
  return { valid: true }
131
136
  }
137
+
138
+ setCrouch(playerId, isCrouching) {
139
+ const data = this.playerBodies.get(playerId)
140
+ if (!data?.charId || !this.physicsWorld) return
141
+ const currentState = this._crouchStates.get(playerId)
142
+ if (currentState === isCrouching) return
143
+ this.physicsWorld.setCharacterCrouch(data.charId, isCrouching)
144
+ this._crouchStates.set(playerId, isCrouching)
145
+ }
132
146
  }
@@ -3,6 +3,9 @@ export class PlayerManager {
3
3
  this.players = new Map()
4
4
  this.nextPlayerId = 1
5
5
  this.inputBuffers = new Map()
6
+ this._connectedCache = null
7
+ this._connectedGen = 0
8
+ this._cachedGen = -1
6
9
  }
7
10
 
8
11
  addPlayer(socket, initialState = {}) {
@@ -17,7 +20,7 @@ export class PlayerManager {
17
20
  velocity: initialState.velocity || [0, 0, 0],
18
21
  angularVelocity: initialState.angularVelocity || [0, 0, 0],
19
22
  onGround: true,
20
- health: 100
23
+ health: initialState.health ?? 100
21
24
  },
22
25
  inputSequence: 0,
23
26
  lastInputTime: 0,
@@ -26,12 +29,14 @@ export class PlayerManager {
26
29
  }
27
30
  this.players.set(playerId, player)
28
31
  this.inputBuffers.set(playerId, [])
32
+ this._connectedGen++
29
33
  return playerId
30
34
  }
31
35
 
32
36
  removePlayer(playerId) {
33
37
  this.players.delete(playerId)
34
38
  this.inputBuffers.delete(playerId)
39
+ this._connectedGen++
35
40
  }
36
41
 
37
42
  getPlayer(playerId) {
@@ -43,7 +48,10 @@ export class PlayerManager {
43
48
  }
44
49
 
45
50
  getConnectedPlayers() {
46
- return this.getAllPlayers().filter(p => p.connected)
51
+ if (this._cachedGen === this._connectedGen) return this._connectedCache
52
+ this._connectedCache = this.getAllPlayers().filter(p => p.connected)
53
+ this._cachedGen = this._connectedGen
54
+ return this._connectedCache
47
55
  }
48
56
 
49
57
  getPlayerCount() {
@@ -2,28 +2,72 @@ function quantize(v, precision) {
2
2
  return Math.round(v * precision) / precision
3
3
  }
4
4
 
5
+ function encodePlayer(p) {
6
+ return [
7
+ p.id,
8
+ quantize(p.position[0], 100), quantize(p.position[1], 100), quantize(p.position[2], 100),
9
+ quantize(p.rotation[0], 10000), quantize(p.rotation[1], 10000), quantize(p.rotation[2], 10000), quantize(p.rotation[3], 10000),
10
+ quantize(p.velocity[0], 100), quantize(p.velocity[1], 100), quantize(p.velocity[2], 100),
11
+ p.onGround ? 1 : 0,
12
+ Math.round(p.health || 0),
13
+ p.inputSequence || 0,
14
+ p.crouch || 0,
15
+ Math.round(((p.lookPitch || 0) + Math.PI) / (2 * Math.PI) * 255),
16
+ Math.round(((p.lookYaw || 0) % (2 * Math.PI) + 2 * Math.PI) % (2 * Math.PI) / (2 * Math.PI) * 255)
17
+ ]
18
+ }
19
+
20
+ function encodeEntity(e) {
21
+ return [
22
+ e.id,
23
+ e.model || '',
24
+ quantize(e.position[0], 100), quantize(e.position[1], 100), quantize(e.position[2], 100),
25
+ quantize(e.rotation[0], 10000), quantize(e.rotation[1], 10000), quantize(e.rotation[2], 10000), quantize(e.rotation[3], 10000),
26
+ e.bodyType || 'static',
27
+ e.custom || null
28
+ ]
29
+ }
30
+
31
+ function entityKey(encoded) {
32
+ let k = ''
33
+ for (let i = 1; i < encoded.length; i++) {
34
+ const v = encoded[i]
35
+ k += v === null ? 'N' : typeof v === 'object' ? JSON.stringify(v) : v
36
+ k += '|'
37
+ }
38
+ return k
39
+ }
40
+
5
41
  export class SnapshotEncoder {
6
42
  static encode(snapshot) {
7
- const players = (snapshot.players || []).map(p => [
8
- p.id,
9
- quantize(p.position[0], 100), quantize(p.position[1], 100), quantize(p.position[2], 100),
10
- quantize(p.rotation[0], 10000), quantize(p.rotation[1], 10000), quantize(p.rotation[2], 10000), quantize(p.rotation[3], 10000),
11
- quantize(p.velocity[0], 100), quantize(p.velocity[1], 100), quantize(p.velocity[2], 100),
12
- p.onGround ? 1 : 0,
13
- Math.round(p.health || 0),
14
- p.inputSequence || 0
15
- ])
16
- const entities = (snapshot.entities || []).map(e => [
17
- e.id,
18
- e.model || '',
19
- quantize(e.position[0], 100), quantize(e.position[1], 100), quantize(e.position[2], 100),
20
- quantize(e.rotation[0], 10000), quantize(e.rotation[1], 10000), quantize(e.rotation[2], 10000), quantize(e.rotation[3], 10000),
21
- e.bodyType || 'static',
22
- e.custom || null
23
- ])
43
+ const players = (snapshot.players || []).map(encodePlayer)
44
+ const entities = (snapshot.entities || []).map(encodeEntity)
24
45
  return { tick: snapshot.tick || 0, timestamp: snapshot.timestamp || 0, players, entities }
25
46
  }
26
47
 
48
+ static encodeDelta(snapshot, prevEntityMap) {
49
+ const players = (snapshot.players || []).map(encodePlayer)
50
+ const currentIds = new Set()
51
+ const entities = []
52
+ const nextMap = new Map()
53
+ for (const e of snapshot.entities || []) {
54
+ const encoded = encodeEntity(e)
55
+ const key = entityKey(encoded)
56
+ currentIds.add(e.id)
57
+ nextMap.set(e.id, key)
58
+ const prev = prevEntityMap.get(e.id)
59
+ if (prev !== key) entities.push(encoded)
60
+ }
61
+ const removed = []
62
+ for (const id of prevEntityMap.keys()) {
63
+ if (!currentIds.has(id)) removed.push(id)
64
+ }
65
+ return {
66
+ encoded: { tick: snapshot.tick || 0, timestamp: snapshot.timestamp || 0, players, entities, removed: removed.length ? removed : undefined, delta: 1 },
67
+ entityMap: nextMap
68
+ }
69
+ }
70
+
27
71
  static decode(data) {
28
72
  if (data.players && Array.isArray(data.players)) {
29
73
  const players = data.players.map(p => {
@@ -31,7 +75,10 @@ export class SnapshotEncoder {
31
75
  id: p[0], position: [p[1], p[2], p[3]],
32
76
  rotation: [p[4], p[5], p[6], p[7]],
33
77
  velocity: [p[8], p[9], p[10]],
34
- onGround: p[11] === 1, health: p[12], inputSequence: p[13]
78
+ onGround: p[11] === 1, health: p[12], inputSequence: p[13],
79
+ crouch: p[14] || 0,
80
+ lookPitch: (p[15] || 0) / 255 * 2 * Math.PI - Math.PI,
81
+ lookYaw: (p[16] || 0) / 255 * 2 * Math.PI
35
82
  }
36
83
  return p
37
84
  })
@@ -42,7 +89,7 @@ export class SnapshotEncoder {
42
89
  }
43
90
  return e
44
91
  })
45
- return { tick: data.tick, timestamp: data.timestamp, players, entities }
92
+ return { tick: data.tick, timestamp: data.timestamp, players, entities, delta: data.delta, removed: data.removed }
46
93
  }
47
94
  return data
48
95
  }
@@ -25,11 +25,13 @@ export class TickSystem {
25
25
  loop() {
26
26
  if (!this.running) return
27
27
  const now = Date.now()
28
- const elapsed = now - this.lastTickTime
29
- if (elapsed >= this.tickDuration && !this._reloadLocked) {
28
+ let elapsed = now - this.lastTickTime
29
+ let steps = 0
30
+ const maxSteps = 4
31
+ while (elapsed >= this.tickDuration && !this._reloadLocked && steps < maxSteps) {
30
32
  this._tickInProgress = true
31
33
  this.currentTick++
32
- this.lastTickTime = now
34
+ this.lastTickTime += this.tickDuration
33
35
  for (const callback of this.callbacks) {
34
36
  callback(this.currentTick, this.tickDuration / 1000)
35
37
  }
@@ -38,8 +40,15 @@ export class TickSystem {
38
40
  this._reloadResolve()
39
41
  this._reloadResolve = null
40
42
  }
43
+ elapsed = now - this.lastTickTime
44
+ steps++
45
+ }
46
+ if (now - this.lastTickTime > this.tickDuration * maxSteps) {
47
+ this.lastTickTime = now
41
48
  }
42
- setImmediate(() => this.loop())
49
+ const gap = this.tickDuration - (Date.now() - this.lastTickTime)
50
+ if (gap > 2) setTimeout(() => this.loop(), 1)
51
+ else setImmediate(() => this.loop())
43
52
  }
44
53
 
45
54
  pauseForReload() {
@@ -6,9 +6,11 @@ async function getJolt() { if (!joltInstance) joltInstance = await initJolt(); r
6
6
  export class PhysicsWorld {
7
7
  constructor(config = {}) {
8
8
  this.gravity = config.gravity || [0, -9.81, 0]
9
+ this.crouchHalfHeight = config.crouchHalfHeight || 0.45
9
10
  this.Jolt = null; this.jolt = null; this.physicsSystem = null; this.bodyInterface = null
10
11
  this.bodies = new Map(); this.bodyMeta = new Map()
11
12
  this._objFilter = null; this._ovbp = null
13
+ this._charShapes = new Map()
12
14
  }
13
15
  async init() {
14
16
  const J = await getJolt()
@@ -99,12 +101,29 @@ export class PhysicsWorld {
99
101
  this._charUpdateSettings.mStickToFloorStepDown = new J.Vec3(0, -0.5, 0)
100
102
  this._charUpdateSettings.mWalkStairsStepUp = new J.Vec3(0, 0.4, 0)
101
103
  this._charGravity = new J.Vec3(this.gravity[0], this.gravity[1], this.gravity[2])
104
+ this._tmpVec3 = new J.Vec3(0, 0, 0)
105
+ this._tmpRVec3 = new J.RVec3(0, 0, 0)
102
106
  }
103
107
  const id = this._nextCharId = (this._nextCharId || 0) + 1
104
108
  if (!this.characters) this.characters = new Map()
105
109
  this.characters.set(id, ch)
110
+ this._charShapes.set(id, { radius, standHeight: halfHeight, crouchHeight: this.crouchHalfHeight })
106
111
  return id
107
112
  }
113
+ setCharacterCrouch(charId, isCrouching) {
114
+ const data = this._charShapes.get(charId)
115
+ if (!data) return
116
+ const heightDiff = (data.standHeight - data.crouchHeight) * 0.5
117
+ const ch = this.characters?.get(charId)
118
+ if (!ch) return
119
+ const pos = this.getCharacterPosition(charId)
120
+ if (isCrouching) {
121
+ pos[1] -= heightDiff
122
+ } else {
123
+ pos[1] += heightDiff
124
+ }
125
+ this.setCharacterPosition(charId, pos)
126
+ }
108
127
  updateCharacter(charId, dt) {
109
128
  const ch = this.characters?.get(charId)
110
129
  if (!ch) return
@@ -113,19 +132,27 @@ export class PhysicsWorld {
113
132
  }
114
133
  getCharacterPosition(charId) {
115
134
  const ch = this.characters?.get(charId); if (!ch) return [0, 0, 0]
116
- const p = ch.GetPosition(); return [p.GetX(), p.GetY(), p.GetZ()]
135
+ const p = ch.GetPosition()
136
+ const r = [p.GetX(), p.GetY(), p.GetZ()]
137
+ this.Jolt.destroy(p)
138
+ return r
117
139
  }
118
140
  getCharacterVelocity(charId) {
119
141
  const ch = this.characters?.get(charId); if (!ch) return [0, 0, 0]
120
- const v = ch.GetLinearVelocity(); return [v.GetX(), v.GetY(), v.GetZ()]
142
+ const v = ch.GetLinearVelocity()
143
+ const r = [v.GetX(), v.GetY(), v.GetZ()]
144
+ this.Jolt.destroy(v)
145
+ return r
121
146
  }
122
147
  setCharacterVelocity(charId, velocity) {
123
148
  const ch = this.characters?.get(charId); if (!ch) return
124
- ch.SetLinearVelocity(new this.Jolt.Vec3(velocity[0], velocity[1], velocity[2]))
149
+ const v = this._tmpVec3; v.Set(velocity[0], velocity[1], velocity[2])
150
+ ch.SetLinearVelocity(v)
125
151
  }
126
152
  setCharacterPosition(charId, position) {
127
153
  const ch = this.characters?.get(charId); if (!ch) return
128
- ch.SetPosition(new this.Jolt.RVec3(position[0], position[1], position[2]))
154
+ const p = this._tmpRVec3; p.Set(position[0], position[1], position[2])
155
+ ch.SetPosition(p)
129
156
  }
130
157
  getCharacterGroundState(charId) {
131
158
  const ch = this.characters?.get(charId); if (!ch) return false
@@ -133,36 +160,53 @@ export class PhysicsWorld {
133
160
  }
134
161
  removeCharacter(charId) {
135
162
  if (!this.characters) return
136
- this.characters.delete(charId)
163
+ const ch = this.characters.get(charId)
164
+ if (ch) {
165
+ this.Jolt.destroy(ch)
166
+ this.characters.delete(charId)
167
+ }
137
168
  }
138
169
  _getBody(bodyId) { return this.bodies.get(bodyId) }
139
170
  getBodyPosition(bodyId) {
140
171
  const b = this._getBody(bodyId); if (!b) return [0, 0, 0]
141
- const p = this.bodyInterface.GetPosition(b.GetID()); return [p.GetX(), p.GetY(), p.GetZ()]
172
+ const p = this.bodyInterface.GetPosition(b.GetID())
173
+ const r = [p.GetX(), p.GetY(), p.GetZ()]
174
+ this.Jolt.destroy(p)
175
+ return r
142
176
  }
143
177
  getBodyRotation(bodyId) {
144
178
  const b = this._getBody(bodyId); if (!b) return [0, 0, 0, 1]
145
- const r = this.bodyInterface.GetRotation(b.GetID()); return [r.GetX(), r.GetY(), r.GetZ(), r.GetW()]
179
+ const q = this.bodyInterface.GetRotation(b.GetID())
180
+ const r = [q.GetX(), q.GetY(), q.GetZ(), q.GetW()]
181
+ this.Jolt.destroy(q)
182
+ return r
146
183
  }
147
184
  getBodyVelocity(bodyId) {
148
185
  const b = this._getBody(bodyId); if (!b) return [0, 0, 0]
149
- const v = this.bodyInterface.GetLinearVelocity(b.GetID()); return [v.GetX(), v.GetY(), v.GetZ()]
186
+ const v = this.bodyInterface.GetLinearVelocity(b.GetID())
187
+ const r = [v.GetX(), v.GetY(), v.GetZ()]
188
+ this.Jolt.destroy(v)
189
+ return r
150
190
  }
151
191
  setBodyPosition(bodyId, position) {
152
192
  const b = this._getBody(bodyId); if (!b) return
153
- this.bodyInterface.SetPosition(b.GetID(), new this.Jolt.RVec3(position[0], position[1], position[2]), this.Jolt.EActivation_Activate)
193
+ const p = this._tmpRVec3 || new this.Jolt.RVec3(0, 0, 0); p.Set(position[0], position[1], position[2])
194
+ this.bodyInterface.SetPosition(b.GetID(), p, this.Jolt.EActivation_Activate)
154
195
  }
155
196
  setBodyVelocity(bodyId, velocity) {
156
197
  const b = this._getBody(bodyId); if (!b) return
157
- this.bodyInterface.SetLinearVelocity(b.GetID(), new this.Jolt.Vec3(velocity[0], velocity[1], velocity[2]))
198
+ const v = this._tmpVec3 || new this.Jolt.Vec3(0, 0, 0); v.Set(velocity[0], velocity[1], velocity[2])
199
+ this.bodyInterface.SetLinearVelocity(b.GetID(), v)
158
200
  }
159
201
  addForce(bodyId, force) {
160
202
  const b = this._getBody(bodyId); if (!b) return
161
- this.bodyInterface.AddForce(b.GetID(), new this.Jolt.Vec3(force[0], force[1], force[2]))
203
+ const v = this._tmpVec3 || new this.Jolt.Vec3(0, 0, 0); v.Set(force[0], force[1], force[2])
204
+ this.bodyInterface.AddForce(b.GetID(), v)
162
205
  }
163
206
  addImpulse(bodyId, impulse) {
164
207
  const b = this._getBody(bodyId); if (!b) return
165
- this.bodyInterface.AddImpulse(b.GetID(), new this.Jolt.Vec3(impulse[0], impulse[1], impulse[2]))
208
+ const v = this._tmpVec3 || new this.Jolt.Vec3(0, 0, 0); v.Set(impulse[0], impulse[1], impulse[2])
209
+ this.bodyInterface.AddImpulse(b.GetID(), v)
166
210
  }
167
211
  step(deltaTime) { if (!this.jolt) return; this.jolt.Step(deltaTime, deltaTime > 1 / 55 ? 2 : 1) }
168
212
  removeBody(bodyId) {
@@ -191,5 +235,14 @@ export class PhysicsWorld {
191
235
  J.destroy(ray); J.destroy(rs); J.destroy(col); J.destroy(bp); J.destroy(ol); J.destroy(bf); J.destroy(sf)
192
236
  return result
193
237
  }
194
- destroy() { for (const [id] of this.bodies) this.removeBody(id); if (this.jolt) { this.Jolt.destroy(this.jolt); this.jolt = null } }
238
+ destroy() {
239
+ if (this.characters) {
240
+ for (const [id, ch] of this.characters) {
241
+ this.Jolt.destroy(ch)
242
+ }
243
+ this.characters.clear()
244
+ }
245
+ for (const [id] of this.bodies) this.removeBody(id)
246
+ if (this.jolt) { this.Jolt.destroy(this.jolt); this.jolt = null }
247
+ }
195
248
  }