spoint 0.1.0 → 0.1.11

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 +31 -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
package/src/sdk/server.js CHANGED
@@ -1,5 +1,6 @@
1
- import { join, dirname, resolve } from 'node:path'
1
+ import { join, dirname, resolve, sep } from 'node:path'
2
2
  import { fileURLToPath, pathToFileURL } from 'node:url'
3
+ import { existsSync } from 'node:fs'
3
4
  import { MSG } from '../protocol/MessageTypes.js'
4
5
  import { ConnectionManager } from '../connection/ConnectionManager.js'
5
6
  import { SessionStore } from '../connection/SessionStore.js'
@@ -24,22 +25,39 @@ import { createServerAPI } from './ServerAPI.js'
24
25
  import { createConnectionHandlers } from './ServerHandlers.js'
25
26
 
26
27
  export async function boot(overrides = {}) {
27
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..')
28
- const worldPath = resolve(ROOT, 'apps/world/index.js')
28
+ const SDK_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..')
29
+ const PROJECT = process.cwd()
30
+ const localWorld = resolve(PROJECT, 'apps/world/index.js')
31
+ const sdkWorld = resolve(SDK_ROOT, 'apps/world/index.js')
32
+ const worldPath = existsSync(localWorld) ? localWorld : sdkWorld
33
+ if (!existsSync(localWorld)) {
34
+ console.log('[boot] no local apps found, using bundled SDK defaults')
35
+ }
29
36
  const worldUrl = pathToFileURL(worldPath).href + `?t=${Date.now()}`
30
37
  const worldMod = await import(worldUrl)
31
38
  const worldDef = worldMod.default || worldMod
39
+ const localApps = resolve(PROJECT, 'apps')
40
+ const sdkApps = join(SDK_ROOT, 'apps')
41
+ const hasLocalApps = existsSync(localApps)
42
+ const appsDirs = hasLocalApps ? [localApps, sdkApps] : [sdkApps]
43
+ const appsStaticDirs = hasLocalApps
44
+ ? [{ prefix: '/apps/', dir: localApps }, { prefix: '/apps/', dir: sdkApps }]
45
+ : [{ prefix: '/apps/', dir: sdkApps }]
46
+ console.debug(`[boot] loading from: ${appsDirs.join(', ')}`)
32
47
  const config = {
33
- port: parseInt(process.env.PORT || String(worldDef.port || 8080), 10),
48
+ port: parseInt(process.env.PORT || String(worldDef.port || 3000), 10),
34
49
  tickRate: worldDef.tickRate || 128,
35
- appsDir: join(ROOT, 'apps'),
50
+ appsDirs,
51
+ sdkRoot: SDK_ROOT,
36
52
  gravity: worldDef.gravity,
37
53
  movement: worldDef.movement,
54
+ playerConfig: worldDef.player,
38
55
  staticDirs: [
39
- { prefix: '/src/', dir: join(ROOT, 'src') },
40
- { prefix: '/world/', dir: join(ROOT, 'world') },
41
- { prefix: '/node_modules/', dir: join(ROOT, 'node_modules') },
42
- { prefix: '/', dir: join(ROOT, 'client') }
56
+ { prefix: '/src/', dir: join(SDK_ROOT, 'src') },
57
+ ...appsStaticDirs,
58
+ { prefix: '/node_modules/', dir: join(SDK_ROOT, 'node_modules') },
59
+ { prefix: '/editor/', dir: join(SDK_ROOT, 'client/editor') },
60
+ { prefix: '/', dir: join(SDK_ROOT, 'client') }
43
61
  ],
44
62
  ...overrides
45
63
  }
@@ -48,42 +66,44 @@ export async function boot(overrides = {}) {
48
66
  server.on('playerJoin', ({ id }) => console.log())
49
67
  server.on('playerLeave', ({ id }) => console.log())
50
68
  const info = await server.start()
51
- console.log()
69
+ console.log(`[server] http://localhost:${info.port} @ ${info.tickRate} TPS`)
52
70
  return server
53
71
  }
54
72
 
55
73
  export async function createServer(config = {}) {
56
- const port = config.port || 8080
74
+ const port = config.port || 3000
57
75
  const tickRate = config.tickRate || 128
58
- const appsDir = config.appsDir || './apps'
76
+ const appsDirs = config.appsDirs || [config.appsDir || './apps']
59
77
  const gravity = config.gravity || [0, -9.81, 0]
60
78
  const movement = config.movement || {}
61
79
  const staticDirs = config.staticDirs || []
62
80
 
63
81
  const storageDir = config.storageDir || './data'
64
- const physics = new PhysicsWorld({ gravity })
82
+ const playerConfig = config.playerConfig || {}
83
+ const physics = new PhysicsWorld({ gravity, crouchHalfHeight: playerConfig.crouchHalfHeight })
65
84
  await physics.init()
66
85
 
67
86
  const emitter = new EventEmitter()
68
87
  const eventBus = new EventBus()
69
- const eventLog = new EventLog()
88
+ const eventLog = new EventLog({ maxSize: 1000 })
70
89
  const storage = new FSAdapter(storageDir)
71
90
  const tickSystem = new TickSystem(tickRate)
72
91
  const playerManager = new PlayerManager()
73
92
  const networkState = new NetworkState()
74
93
  const lagCompensator = new LagCompensator()
75
- const physicsIntegration = new PhysicsIntegration({ gravity, physicsWorld: physics })
94
+ const physicsIntegration = new PhysicsIntegration({ gravity, physicsWorld: physics, capsuleRadius: playerConfig.capsuleRadius, capsuleHalfHeight: playerConfig.capsuleHalfHeight, crouchHalfHeight: playerConfig.crouchHalfHeight, playerMass: playerConfig.mass })
76
95
  const connections = new ConnectionManager({
77
96
  heartbeatInterval: config.heartbeatInterval || 1000,
78
- heartbeatTimeout: config.heartbeatTimeout || 3000
97
+ heartbeatTimeout: config.heartbeatTimeout || 10000
79
98
  })
80
- const sessions = new SessionStore({ ttl: config.sessionTTL || 30000 })
99
+ const sessions = new SessionStore({ ttl: config.sessionTTL || 60000 })
81
100
  const inspector = new Inspector()
82
101
  const reloadManager = new ReloadManager()
83
102
 
84
- const appRuntime = new AppRuntime({ gravity, playerManager, physics, physicsIntegration, connections, eventBus, eventLog, storage })
103
+ const sdkRoot = config.sdkRoot || join(dirname(fileURLToPath(import.meta.url)), '../..')
104
+ const appRuntime = new AppRuntime({ gravity, playerManager, physics, physicsIntegration, connections, eventBus, eventLog, storage, sdkRoot })
85
105
  appRuntime.setPlayerManager(playerManager)
86
- const appLoader = new AppLoader(appRuntime, { dir: appsDir })
106
+ const appLoader = new AppLoader(appRuntime, { dirs: appsDirs })
87
107
  const stageLoader = new StageLoader(appRuntime)
88
108
  appRuntime.setStageLoader(stageLoader)
89
109
 
@@ -95,7 +115,7 @@ export async function createServer(config = {}) {
95
115
  config,
96
116
  port,
97
117
  tickRate,
98
- appsDir,
118
+ appsDirs,
99
119
  gravity,
100
120
  movement,
101
121
  staticDirs,
@@ -127,6 +147,7 @@ export async function createServer(config = {}) {
127
147
  setTickHandler: (fn) => { ctx.handlerState.fn = fn }
128
148
  }
129
149
 
150
+ const worldConfigUrl = pathToFileURL(existsSync(resolve(process.cwd(), 'apps/world/index.js')) ? resolve(process.cwd(), 'apps/world/index.js') : join(dirname(fileURLToPath(import.meta.url)), '../../apps/world/index.js')).href
130
151
  const reloadHandlers = createReloadHandlers({
131
152
  networkState,
132
153
  playerManager,
@@ -134,7 +155,9 @@ export async function createServer(config = {}) {
134
155
  lagCompensator,
135
156
  physics,
136
157
  appRuntime,
137
- connections
158
+ connections,
159
+ movement,
160
+ worldConfigPath: worldConfigUrl
138
161
  })
139
162
  ctx.reloadHandlers = reloadHandlers
140
163
 
@@ -156,23 +179,26 @@ export async function createServer(config = {}) {
156
179
 
157
180
  ctx.setupSDKWatchers = () => {
158
181
  const reloadTick = async () => { ctx.setTickHandler(await reloadHandlers.reloadTickHandler()) }
182
+ const sdk = (p) => join(sdkRoot, p)
159
183
  const w = [
160
- ['tick-handler', './src/sdk/TickHandler.js', reloadTick],
161
- ['physics-integration', './src/netcode/PhysicsIntegration.js', reloadHandlers.reloadPhysicsIntegration],
162
- ['lag-compensator', './src/netcode/LagCompensator.js', reloadHandlers.reloadLagCompensator],
163
- ['player-manager', './src/netcode/PlayerManager.js', reloadHandlers.reloadPlayerManager],
164
- ['network-state', './src/netcode/NetworkState.js', reloadHandlers.reloadNetworkState]
184
+ ['tick-handler', sdk('src/sdk/TickHandler.js'), reloadTick],
185
+ ['movement', sdk('src/shared/movement.js'), reloadTick],
186
+ ['world-config', sdk('apps/world/index.js'), reloadTick],
187
+ ['physics-integration', sdk('src/netcode/PhysicsIntegration.js'), reloadHandlers.reloadPhysicsIntegration],
188
+ ['lag-compensator', sdk('src/netcode/LagCompensator.js'), reloadHandlers.reloadLagCompensator],
189
+ ['player-manager', sdk('src/netcode/PlayerManager.js'), reloadHandlers.reloadPlayerManager],
190
+ ['network-state', sdk('src/netcode/NetworkState.js'), reloadHandlers.reloadNetworkState]
165
191
  ]
166
192
  for (const [id, path, reload] of w) reloadManager.addWatcher(id, path, reload)
167
193
  const clientReload = () => { connections.broadcast(MSG.HOT_RELOAD, { timestamp: Date.now() }) }
168
194
  const clientFiles = [
169
- ['client-app', './client/app.js'],
170
- ['client-camera', './client/camera.js'],
171
- ['client-input', './src/client/InputHandler.js'],
172
- ['client-network', './src/client/PhysicsNetworkClient.js'],
173
- ['client-prediction', './src/client/PredictionEngine.js'],
174
- ['client-reconciliation', './src/client/ReconciliationEngine.js'],
175
- ['client-index', './src/index.client.js']
195
+ ['client-app', sdk('client/app.js')],
196
+ ['client-camera', sdk('client/camera.js')],
197
+ ['client-input', sdk('src/client/InputHandler.js')],
198
+ ['client-network', sdk('src/client/PhysicsNetworkClient.js')],
199
+ ['client-prediction', sdk('src/client/PredictionEngine.js')],
200
+ ['client-reconciliation', sdk('src/client/ReconciliationEngine.js')],
201
+ ['client-index', sdk('src/index.client.js')]
176
202
  ]
177
203
  for (const [id, path] of clientFiles) reloadManager.addWatcher(id, path, clientReload)
178
204
  }
@@ -15,7 +15,8 @@ export function applyMovement(state, input, movement, dt) {
15
15
  const cy = Math.cos(yaw), sy = Math.sin(yaw)
16
16
  wishX = fz * sy - fx * cy
17
17
  wishZ = fx * sy + fz * cy
18
- wishSpeed = flen > 0 ? maxSpeed : 0
18
+ const baseSpeed = input.crouch ? maxSpeed * (movement.crouchSpeedMul || 0.4) : maxSpeed
19
+ wishSpeed = flen > 0 ? (input.sprint && !input.crouch ? (movement.sprintSpeed || maxSpeed * 1.75) : baseSpeed) : 0
19
20
  if (input.jump && state.onGround) {
20
21
  state.velocity[1] = jumpImpulse
21
22
  state.onGround = false
@@ -23,6 +23,11 @@ export class SpatialIndex {
23
23
  }
24
24
 
25
25
  update(id, position) {
26
+ const existing = this._entities.get(id)
27
+ if (existing) {
28
+ const dx = existing[0] - position[0], dy = existing[1] - position[1], dz = existing[2] - position[2]
29
+ if (dx * dx + dy * dy + dz * dz < 0.001) return
30
+ }
26
31
  this.insert(id, position)
27
32
  }
28
33
 
@@ -1,33 +0,0 @@
1
- export default {
2
- server: {
3
- setup(ctx) {
4
- ctx.state.open = false
5
- ctx.state.openAngle = Math.PI / 2
6
- ctx.physics.setKinematic(true)
7
- ctx.physics.addBoxCollider([2, 3, 0.2])
8
- },
9
- update(ctx, dt) {
10
- const nearest = ctx.players.getNearest(ctx.entity.position, 3)
11
- if (nearest && !ctx.state.open) {
12
- ctx.state.open = true
13
- const rot = ctx.entity.rotation
14
- rot[1] = Math.sin(ctx.state.openAngle / 2)
15
- rot[3] = Math.cos(ctx.state.openAngle / 2)
16
- ctx.entity.rotation = rot
17
- } else if (!nearest && ctx.state.open) {
18
- ctx.state.open = false
19
- ctx.entity.rotation = [0, 0, 0, 1]
20
- }
21
- }
22
- },
23
- client: {
24
- render(ctx) {
25
- return {
26
- model: ctx.entity.model,
27
- position: ctx.entity.position,
28
- rotation: ctx.entity.rotation,
29
- custom: { doorOpen: ctx.state.open }
30
- }
31
- }
32
- }
33
- }
@@ -1,37 +0,0 @@
1
- export default {
2
- server: {
3
- setup(ctx) {
4
- ctx.state.waypoints = [[0, 0, 0], [10, 0, 0], [10, 0, 10], [0, 0, 10]]
5
- ctx.state.current = 0
6
- ctx.state.speed = 3
7
- ctx.physics.setKinematic(true)
8
- ctx.physics.addCapsuleCollider(0.5, 1.8)
9
- },
10
- update(ctx, dt) {
11
- const target = ctx.state.waypoints[ctx.state.current]
12
- const pos = ctx.entity.position
13
- const dx = target[0] - pos[0]
14
- const dz = target[2] - pos[2]
15
- const dist = Math.sqrt(dx * dx + dz * dz)
16
- if (dist < 0.5) {
17
- ctx.state.current = (ctx.state.current + 1) % ctx.state.waypoints.length
18
- } else {
19
- const step = ctx.state.speed * dt
20
- pos[0] += (dx / dist) * step
21
- pos[2] += (dz / dist) * step
22
- const angle = Math.atan2(dx, dz)
23
- ctx.entity.rotation = [0, Math.sin(angle / 2), 0, Math.cos(angle / 2)]
24
- }
25
- }
26
- },
27
- client: {
28
- render(ctx) {
29
- return {
30
- model: ctx.entity.model,
31
- position: ctx.entity.position,
32
- rotation: ctx.entity.rotation,
33
- custom: { animation: 'walk' }
34
- }
35
- }
36
- }
37
- }
@@ -1,46 +0,0 @@
1
- export class QualityMonitor {
2
- constructor() {
3
- this.bytesIn = 0
4
- this.bytesOut = 0
5
- this.heartbeatsSent = 0
6
- this.heartbeatsReceived = 0
7
- this.rttSamples = []
8
- this.startTime = Date.now()
9
- }
10
-
11
- recordBytesIn(count) {
12
- this.bytesIn += count
13
- }
14
-
15
- recordBytesOut(count) {
16
- this.bytesOut += count
17
- }
18
-
19
- recordHeartbeatSent() {
20
- this.heartbeatsSent++
21
- }
22
-
23
- recordHeartbeatReceived() {
24
- this.heartbeatsReceived++
25
- }
26
-
27
- recordRtt(rtt) {
28
- this.rttSamples.push(rtt)
29
- if (this.rttSamples.length > 100) this.rttSamples.shift()
30
- }
31
-
32
- getStats() {
33
- const elapsed = Date.now() - this.startTime
34
- const avgRtt = this.rttSamples.length > 0
35
- ? this.rttSamples.reduce((a, b) => a + b, 0) / this.rttSamples.length
36
- : 0
37
- return {
38
- bytesIn: this.bytesIn,
39
- bytesOut: this.bytesOut,
40
- heartbeatsSent: this.heartbeatsSent,
41
- heartbeatsReceived: this.heartbeatsReceived,
42
- avgRtt,
43
- uptime: elapsed
44
- }
45
- }
46
- }
@@ -1,42 +0,0 @@
1
- export class StateInspector {
2
- constructor() {
3
- this.snapshots = []
4
- this.delays = []
5
- this.corrections = []
6
- this.maxSnapshots = 100
7
- }
8
-
9
- recordSnapshot(snap) {
10
- this.snapshots.push({ ...snap, timestamp: Date.now() })
11
- if (this.snapshots.length > this.maxSnapshots) {
12
- this.snapshots.shift()
13
- }
14
- }
15
-
16
- recordSnapshotDelay(delay) {
17
- this.delays.push(delay)
18
- if (this.delays.length > this.maxSnapshots) {
19
- this.delays.shift()
20
- }
21
- }
22
-
23
- recordCorrection(playerId, oldState, newState, deviationMs) {
24
- this.corrections.push({ playerId, newState, deviationMs, timestamp: Date.now() })
25
- if (this.corrections.length > this.maxSnapshots) {
26
- this.corrections.shift()
27
- }
28
- }
29
-
30
- getStats() {
31
- const avgDelay = this.delays.length > 0
32
- ? this.delays.reduce((a, b) => a + b, 0) / this.delays.length
33
- : 0
34
- return {
35
- snapshots: this.snapshots.length,
36
- delays: this.delays.length,
37
- avgDelay,
38
- corrections: this.corrections.length,
39
- lastSnapshot: this.snapshots[this.snapshots.length - 1] || null
40
- }
41
- }
42
- }
package/src/index.js DELETED
@@ -1 +0,0 @@
1
- export * from './index.server.js'
@@ -1,27 +0,0 @@
1
- export { PhysicsWorld } from './physics/World.js'
2
-
3
- export { AppRuntime } from './apps/AppRuntime.js'
4
- export { EventBus } from './apps/EventBus.js'
5
- export { StageLoader } from './stage/StageLoader.js'
6
- export { Stage } from './stage/Stage.js'
7
- export { SpatialIndex } from './spatial/Octree.js'
8
-
9
- export { TickSystem } from './netcode/TickSystem.js'
10
- export { NetworkState } from './netcode/NetworkState.js'
11
- export { SnapshotEncoder } from './netcode/SnapshotEncoder.js'
12
- export { PlayerManager } from './netcode/PlayerManager.js'
13
- export { LagCompensator } from './netcode/LagCompensator.js'
14
- export { PhysicsIntegration } from './netcode/PhysicsIntegration.js'
15
- export { EventLog } from './netcode/EventLog.js'
16
-
17
- export { StorageAdapter } from './storage/StorageAdapter.js'
18
- export { FSAdapter } from './storage/FSAdapter.js'
19
-
20
- export { MSG, msgName, DISCONNECT_REASONS, CONNECTION_QUALITY, UNRELIABLE_MSGS, isUnreliable } from './protocol/MessageTypes.js'
21
- export { Codec } from './protocol/Codec.js'
22
- export { SequenceTracker } from './protocol/SequenceTracker.js'
23
-
24
- export { TransportWrapper } from './transport/TransportWrapper.js'
25
- export { WebSocketTransport } from './transport/WebSocketTransport.js'
26
- export { WebTransportTransport } from './transport/WebTransportTransport.js'
27
- export { WebTransportServer, WEBTRANSPORT_AVAILABLE } from './transport/WebTransportServer.js'
@@ -1,60 +0,0 @@
1
- import { pack, unpack } from './msgpack.js'
2
-
3
- function toUint8(input) {
4
- if (input instanceof Uint8Array) return input
5
- if (input instanceof ArrayBuffer) return new Uint8Array(input)
6
- if (typeof Buffer !== 'undefined' && Buffer.isBuffer(input)) return new Uint8Array(input.buffer, input.byteOffset, input.byteLength)
7
- return new Uint8Array(input)
8
- }
9
-
10
- export class Codec {
11
- constructor() {
12
- this.bytesSent = 0
13
- this.bytesReceived = 0
14
- this.messagesSent = 0
15
- this.messagesReceived = 0
16
- this.sendSequence = 0
17
- }
18
-
19
- encode(type, payload) {
20
- const seq = this.sendSequence++ & 0xFFFF
21
- const body = pack(payload)
22
- const bodyBytes = toUint8(body)
23
- const frame = new Uint8Array(3 + bodyBytes.length)
24
- frame[0] = type
25
- frame[1] = (seq >> 8) & 0xFF
26
- frame[2] = seq & 0xFF
27
- frame.set(bodyBytes, 3)
28
- this.bytesSent += frame.length
29
- this.messagesSent++
30
- return frame
31
- }
32
-
33
- decode(buffer) {
34
- const buf = toUint8(buffer)
35
- if (buf.length < 3) return null
36
- const type = buf[0]
37
- const seq = (buf[1] << 8) | buf[2]
38
- const payload = buf.length > 3 ? unpack(buf.slice(3)) : null
39
- this.bytesReceived += buf.length
40
- this.messagesReceived++
41
- return { type, seq, payload }
42
- }
43
-
44
- getStats() {
45
- return {
46
- bytesSent: this.bytesSent,
47
- bytesReceived: this.bytesReceived,
48
- messagesSent: this.messagesSent,
49
- messagesReceived: this.messagesReceived,
50
- sendSequence: this.sendSequence
51
- }
52
- }
53
-
54
- resetStats() {
55
- this.bytesSent = 0
56
- this.bytesReceived = 0
57
- this.messagesSent = 0
58
- this.messagesReceived = 0
59
- }
60
- }
@@ -1,71 +0,0 @@
1
- export class SequenceTracker {
2
- constructor(windowSize = 256) {
3
- this.windowSize = windowSize
4
- this.nextExpected = 0
5
- this.received = new Set()
6
- this.outOfOrder = []
7
- this.totalReceived = 0
8
- this.totalExpected = 0
9
- this.gaps = 0
10
- }
11
-
12
- track(seq) {
13
- this.totalReceived++
14
- if (seq === this.nextExpected) {
15
- this.nextExpected = (this.nextExpected + 1) & 0xFFFF
16
- this._drainOutOfOrder()
17
- return { inOrder: true, gap: 0 }
18
- }
19
- const gap = (seq - this.nextExpected + 0x10000) & 0xFFFF
20
- if (gap > this.windowSize) {
21
- this.nextExpected = (seq + 1) & 0xFFFF
22
- this.outOfOrder = []
23
- this.received.clear()
24
- return { inOrder: true, gap: 0, reset: true }
25
- }
26
- this.gaps++
27
- this.outOfOrder.push(seq)
28
- this.outOfOrder.sort((a, b) => {
29
- const da = (a - this.nextExpected + 0x10000) & 0xFFFF
30
- const db = (b - this.nextExpected + 0x10000) & 0xFFFF
31
- return da - db
32
- })
33
- this.received.add(seq)
34
- return { inOrder: false, gap }
35
- }
36
-
37
- _drainOutOfOrder() {
38
- while (this.outOfOrder.length > 0 && this.outOfOrder[0] === this.nextExpected) {
39
- this.outOfOrder.shift()
40
- this.received.delete(this.nextExpected)
41
- this.nextExpected = (this.nextExpected + 1) & 0xFFFF
42
- }
43
- }
44
-
45
- getPacketLoss() {
46
- if (this.totalReceived === 0) return 0
47
- const expected = this.nextExpected
48
- if (expected === 0) return 0
49
- const lost = expected - this.totalReceived
50
- return Math.max(0, lost / expected)
51
- }
52
-
53
- getStats() {
54
- return {
55
- nextExpected: this.nextExpected,
56
- totalReceived: this.totalReceived,
57
- outOfOrderCount: this.outOfOrder.length,
58
- gapEvents: this.gaps,
59
- packetLoss: this.getPacketLoss()
60
- }
61
- }
62
-
63
- reset() {
64
- this.nextExpected = 0
65
- this.received.clear()
66
- this.outOfOrder = []
67
- this.totalReceived = 0
68
- this.totalExpected = 0
69
- this.gaps = 0
70
- }
71
- }
@@ -1,80 +0,0 @@
1
- import { MSG } from '../protocol/MessageTypes.js'
2
- import { SnapshotEncoder } from '../netcode/SnapshotEncoder.js'
3
- import { unpack } from '../protocol/msgpack.js'
4
-
5
- export function createMessageRouter(deps) {
6
- const { emitter, quality, stateInspector, send, getState, setState } = deps
7
-
8
- function handleSnapshot(data) {
9
- try {
10
- const decoded = SnapshotEncoder.decode(data)
11
- if (decoded.tick) {
12
- stateInspector.recordSnapshotDelay(Date.now() - (decoded.timestamp || 0))
13
- setState('tick', decoded.tick)
14
- }
15
- for (const p of decoded.players || []) getState('players').set(p.id, p)
16
- for (const e of decoded.entities || []) getState('entities').set(e.id, e)
17
- emitter.emit('snapshot', decoded)
18
- } catch (e) {
19
- emitter.emit('error', e)
20
- }
21
- }
22
-
23
- return function onMessage(raw) {
24
- const buf = raw instanceof ArrayBuffer ? new Uint8Array(raw) : (raw instanceof Uint8Array ? raw : new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength))
25
- let msg
26
- try { msg = unpack(buf) } catch (e) { return }
27
- if (!msg || typeof msg !== 'object') return
28
- const byteLen = buf.length || buf.byteLength || 0
29
- quality.recordBytesIn(byteLen)
30
-
31
- if (msg.type === MSG.HEARTBEAT) {
32
- send(MSG.HEARTBEAT_ACK, { ts: msg.payload?.ts })
33
- return
34
- }
35
- if (msg.type === MSG.HEARTBEAT_ACK && msg.payload?.ts) {
36
- quality.recordRtt(Date.now() - msg.payload.ts)
37
- quality.recordHeartbeatReceived()
38
- return
39
- }
40
- if (msg.type === MSG.HANDSHAKE_ACK) {
41
- setState('playerId', msg.payload.playerId)
42
- setState('sessionToken', msg.payload.sessionToken)
43
- setState('tick', msg.payload.tick)
44
- emitter.emit('connect', { playerId: msg.payload.playerId, tick: msg.payload.tick })
45
- return
46
- }
47
- if (msg.type === MSG.SNAPSHOT) { handleSnapshot(msg.payload); return }
48
- if (msg.type === MSG.PLAYER_LEAVE) {
49
- getState('players').delete(msg.payload.playerId)
50
- emitter.emit('playerLeave', msg.payload.playerId)
51
- return
52
- }
53
- if (msg.type === MSG.RECONNECT_ACK) {
54
- setState('playerId', msg.payload.playerId)
55
- setState('sessionToken', msg.payload.sessionToken)
56
- setState('tick', msg.payload.tick)
57
- setState('reconnecting', false)
58
- setState('reconnectAttempts', 0)
59
- emitter.emit('reconnect', msg.payload)
60
- return
61
- }
62
- if (msg.type === MSG.STATE_RECOVERY) {
63
- const decoded = SnapshotEncoder.decode(msg.payload.snapshot)
64
- for (const p of decoded.players || []) getState('players').set(p.id, p)
65
- for (const e of decoded.entities || []) getState('entities').set(e.id, e)
66
- emitter.emit('stateRecovery', decoded)
67
- return
68
- }
69
- if (msg.type === MSG.STATE_CORRECTION) {
70
- stateInspector.recordCorrection(getState('playerId'), null, msg.payload, 0)
71
- emitter.emit('correction', msg.payload)
72
- return
73
- }
74
- if (msg.type === MSG.DISCONNECT_REASON) {
75
- emitter.emit('disconnectReason', msg.payload)
76
- return
77
- }
78
- emitter.emit('message', msg)
79
- }
80
- }