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
@@ -1,74 +1,101 @@
1
1
  const encoder = new TextEncoder()
2
2
  const decoder = new TextDecoder()
3
+ const f64Buf = new ArrayBuffer(8)
4
+ const f64View = new DataView(f64Buf)
5
+ const f64Bytes = new Uint8Array(f64Buf)
3
6
 
4
- export function pack(value) {
5
- const chunks = []
6
- function write(value) {
7
- if (value === null || value === undefined) {
8
- chunks.push(0xc0)
9
- } else if (value === false) {
10
- chunks.push(0xc2)
11
- } else if (value === true) {
12
- chunks.push(0xc3)
13
- } else if (typeof value === 'number') {
14
- if (Number.isInteger(value)) {
15
- if (value >= 0) {
16
- if (value < 128) chunks.push(value)
17
- else if (value < 256) chunks.push(0xcc, value)
18
- else if (value < 65536) chunks.push(0xcd, value >> 8, value & 0xff)
19
- else if (value < 4294967296) chunks.push(0xce, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
20
- else writeFloat64(value)
21
- } else {
22
- if (value >= -32) chunks.push(value & 0xff)
23
- else if (value >= -128) chunks.push(0xd0, value & 0xff)
24
- else if (value >= -32768) chunks.push(0xd1, (value >> 8) & 0xff, value & 0xff)
25
- else if (value >= -2147483648) chunks.push(0xd2, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
26
- else writeFloat64(value)
27
- }
7
+ let buf = new Uint8Array(4096)
8
+ let pos = 0
9
+
10
+ function grow(need) {
11
+ if (pos + need <= buf.length) return
12
+ let size = buf.length
13
+ while (size < pos + need) size *= 2
14
+ const next = new Uint8Array(size)
15
+ next.set(buf.subarray(0, pos))
16
+ buf = next
17
+ }
18
+
19
+ function w1(v) { grow(1); buf[pos++] = v }
20
+ function w2(a, b) { grow(2); buf[pos++] = a; buf[pos++] = b }
21
+ function w3(a, b, c) { grow(3); buf[pos++] = a; buf[pos++] = b; buf[pos++] = c }
22
+ function w5(a, b, c, d, e) { grow(5); buf[pos++] = a; buf[pos++] = b; buf[pos++] = c; buf[pos++] = d; buf[pos++] = e }
23
+
24
+ function writeFloat64(value) {
25
+ grow(9)
26
+ buf[pos++] = 0xcb
27
+ f64View.setFloat64(0, value, false)
28
+ buf[pos++] = f64Bytes[0]; buf[pos++] = f64Bytes[1]; buf[pos++] = f64Bytes[2]; buf[pos++] = f64Bytes[3]
29
+ buf[pos++] = f64Bytes[4]; buf[pos++] = f64Bytes[5]; buf[pos++] = f64Bytes[6]; buf[pos++] = f64Bytes[7]
30
+ }
31
+
32
+ function write(value) {
33
+ if (value === null || value === undefined) {
34
+ w1(0xc0)
35
+ } else if (value === false) {
36
+ w1(0xc2)
37
+ } else if (value === true) {
38
+ w1(0xc3)
39
+ } else if (typeof value === 'number') {
40
+ if (Number.isInteger(value)) {
41
+ if (value >= 0) {
42
+ if (value < 128) w1(value)
43
+ else if (value < 256) w2(0xcc, value)
44
+ else if (value < 65536) w3(0xcd, value >> 8, value & 0xff)
45
+ else if (value < 4294967296) w5(0xce, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
46
+ else writeFloat64(value)
28
47
  } else {
29
- writeFloat64(value)
48
+ if (value >= -32) w1(value & 0xff)
49
+ else if (value >= -128) w2(0xd0, value & 0xff)
50
+ else if (value >= -32768) w3(0xd1, (value >> 8) & 0xff, value & 0xff)
51
+ else if (value >= -2147483648) w5(0xd2, (value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff)
52
+ else writeFloat64(value)
30
53
  }
31
- } else if (typeof value === 'string') {
32
- const bytes = encoder.encode(value)
33
- const len = bytes.length
34
- if (len < 32) chunks.push(0xa0 | len)
35
- else if (len < 256) chunks.push(0xd9, len)
36
- else if (len < 65536) chunks.push(0xda, len >> 8, len & 0xff)
37
- else chunks.push(0xdb, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
38
- for (let i = 0; i < len; i++) chunks.push(bytes[i])
39
- } else if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
40
- const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value
41
- const len = bytes.length
42
- if (len < 256) chunks.push(0xc4, len)
43
- else if (len < 65536) chunks.push(0xc5, len >> 8, len & 0xff)
44
- else chunks.push(0xc6, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
45
- for (let i = 0; i < len; i++) chunks.push(bytes[i])
46
- } else if (Array.isArray(value)) {
47
- const len = value.length
48
- if (len < 16) chunks.push(0x90 | len)
49
- else if (len < 65536) chunks.push(0xdc, len >> 8, len & 0xff)
50
- else chunks.push(0xdd, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
51
- for (let i = 0; i < len; i++) write(value[i])
52
- } else if (typeof value === 'object') {
53
- const keys = Object.keys(value)
54
- const len = keys.length
55
- if (len < 16) chunks.push(0x80 | len)
56
- else if (len < 65536) chunks.push(0xde, len >> 8, len & 0xff)
57
- else chunks.push(0xdf, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
58
- for (const key of keys) { write(key); write(value[key]) }
54
+ } else {
55
+ writeFloat64(value)
59
56
  }
57
+ } else if (typeof value === 'string') {
58
+ const bytes = encoder.encode(value)
59
+ const len = bytes.length
60
+ if (len < 32) w1(0xa0 | len)
61
+ else if (len < 256) w2(0xd9, len)
62
+ else if (len < 65536) w3(0xda, len >> 8, len & 0xff)
63
+ else w5(0xdb, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
64
+ grow(len)
65
+ buf.set(bytes, pos)
66
+ pos += len
67
+ } else if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
68
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value
69
+ const len = bytes.length
70
+ if (len < 256) w2(0xc4, len)
71
+ else if (len < 65536) w3(0xc5, len >> 8, len & 0xff)
72
+ else w5(0xc6, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
73
+ grow(len)
74
+ buf.set(bytes, pos)
75
+ pos += len
76
+ } else if (Array.isArray(value)) {
77
+ const len = value.length
78
+ if (len < 16) w1(0x90 | len)
79
+ else if (len < 65536) w3(0xdc, len >> 8, len & 0xff)
80
+ else w5(0xdd, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
81
+ for (let i = 0; i < len; i++) write(value[i])
82
+ } else if (typeof value === 'object') {
83
+ const keys = Object.keys(value)
84
+ const len = keys.length
85
+ if (len < 16) w1(0x80 | len)
86
+ else if (len < 65536) w3(0xde, len >> 8, len & 0xff)
87
+ else w5(0xdf, (len >> 24) & 0xff, (len >> 16) & 0xff, (len >> 8) & 0xff, len & 0xff)
88
+ for (const key of keys) { write(key); write(value[key]) }
60
89
  }
61
- function writeFloat64(value) {
62
- chunks.push(0xcb)
63
- const buf = new ArrayBuffer(8)
64
- new DataView(buf).setFloat64(0, value, false)
65
- const bytes = new Uint8Array(buf)
66
- for (let i = 0; i < 8; i++) chunks.push(bytes[i])
67
- }
90
+ }
91
+
92
+ export function pack(value) {
93
+ pos = 0
68
94
  write(value)
69
- return new Uint8Array(chunks)
95
+ return buf.slice(0, pos)
70
96
  }
71
97
 
98
+
72
99
  export function unpack(buffer) {
73
100
  const bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer)
74
101
  let offset = 0
@@ -109,8 +136,8 @@ export function unpack(buffer) {
109
136
  function readInt8() { const v = bytes[offset++]; return v > 127 ? v - 256 : v }
110
137
  function readInt16() { const v = (bytes[offset] << 8) | bytes[offset + 1]; offset += 2; return v > 32767 ? v - 65536 : v }
111
138
  function readInt32() { const v = (bytes[offset] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; offset += 4; return v }
112
- function readFloat32() { const buf = new ArrayBuffer(4); const view = new Uint8Array(buf); for (let i = 0; i < 4; i++) view[i] = bytes[offset++]; return new DataView(buf).getFloat32(0, false) }
113
- function readFloat64() { const buf = new ArrayBuffer(8); const view = new Uint8Array(buf); for (let i = 0; i < 8; i++) view[i] = bytes[offset++]; return new DataView(buf).getFloat64(0, false) }
139
+ function readFloat32() { const b = new ArrayBuffer(4); const v = new Uint8Array(b); for (let i = 0; i < 4; i++) v[i] = bytes[offset++]; return new DataView(b).getFloat32(0, false) }
140
+ function readFloat64() { const b = new ArrayBuffer(8); const v = new Uint8Array(b); for (let i = 0; i < 8; i++) v[i] = bytes[offset++]; return new DataView(b).getFloat64(0, false) }
114
141
  function readString(len) { const slice = bytes.subarray(offset, offset + len); offset += len; return decoder.decode(slice) }
115
142
  function readBin(len) { const slice = bytes.slice(offset, offset + len); offset += len; return slice }
116
143
  function readArray(len) { const arr = new Array(len); for (let i = 0; i < len; i++) arr[i] = read(); return arr }
@@ -34,8 +34,18 @@ export function createReloadHandlers(deps) {
34
34
  } = deps
35
35
 
36
36
  const reloadTickHandler = async () => {
37
- const { createTickHandler: refreshHandler } = await import('./TickHandler.js?' + Date.now())
38
- return refreshHandler(deps)
37
+ const t = Date.now()
38
+ const { applyMovement, DEFAULT_MOVEMENT } = await import('../shared/movement.js?' + t)
39
+ const { createTickHandler: refreshHandler } = await import('./TickHandler.js?' + t)
40
+ let movement = deps.movement
41
+ if (deps.worldConfigPath) {
42
+ try {
43
+ const wm = await import(deps.worldConfigPath + '?' + t)
44
+ const wd = wm.default || wm
45
+ if (wd.movement) movement = wd.movement
46
+ } catch (e) {}
47
+ }
48
+ return refreshHandler({ ...deps, movement, _movement: { applyMovement, DEFAULT_MOVEMENT } })
39
49
  }
40
50
 
41
51
  const reloadPhysicsIntegration = async () => {
@@ -1,4 +1,5 @@
1
1
  import { watch } from 'node:fs/promises'
2
+ import { existsSync } from 'node:fs'
2
3
  import { resolve } from 'node:path'
3
4
 
4
5
  export class ReloadManager {
@@ -16,6 +17,10 @@ export class ReloadManager {
16
17
  addWatcher(moduleId, filePath, onReload, validator) {
17
18
  const absPath = resolve(filePath)
18
19
  if (this._watchers.has(moduleId)) return
20
+ if (!existsSync(absPath)) {
21
+ console.debug(`[ReloadManager] skipping watch for missing file: ${moduleId}`)
22
+ return
23
+ }
19
24
  this._reloadState.set(moduleId, { inProgress: false, lastSuccess: null, failureCount: 0 })
20
25
  this._failureCounters.set(moduleId, 0)
21
26
  if (validator) this._validators.set(moduleId, validator)
@@ -6,9 +6,10 @@ export function createConnectionHandlers(ctx) {
6
6
 
7
7
  function onClientConnect(transport) {
8
8
  const sp = [...ctx.worldSpawnPoint]
9
- const playerId = playerManager.addPlayer(transport, { position: sp })
9
+ const playerConfig = ctx.currentWorldDef?.player || {}
10
+ const playerId = playerManager.addPlayer(transport, { position: sp, health: playerConfig.health })
10
11
  networkState.addPlayer(playerId, { position: sp })
11
- physicsIntegration.addPlayerCollider(playerId, 0.4)
12
+ physicsIntegration.addPlayerCollider(playerId, playerConfig.capsuleRadius || 0.4)
12
13
  physicsIntegration.setPlayerPosition(playerId, sp)
13
14
  const playerState = playerManager.getPlayer(playerId).state
14
15
  lagCompensator.recordPlayerPosition(playerId, playerState.position, playerState.rotation, playerState.velocity, tickSystem.currentTick)
@@ -24,23 +25,25 @@ export function createConnectionHandlers(ctx) {
24
25
  }
25
26
  const snap = appRuntime.getSnapshot()
26
27
  connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(snap) })
27
- appRuntime.fireMessage('game', { type: 'player_join', playerId })
28
+ for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId })
28
29
  emitter.emit('playerJoin', { id: playerId })
29
30
  }
30
31
 
31
32
  connections.on('message', (clientId, msg) => {
32
33
  if (inspector.handleMessage(clientId, msg)) return
34
+ if (msg.type === MSG.HEARTBEAT) {
35
+ connections.send(clientId, MSG.HEARTBEAT_ACK, { timestamp: msg.payload?.timestamp || Date.now() })
36
+ return
37
+ }
33
38
  if (msg.type === MSG.INPUT || msg.type === MSG.PLAYER_INPUT) {
34
39
  playerManager.addInput(clientId, msg.payload?.input || msg.payload)
35
40
  return
36
41
  }
37
42
  if (msg.type === MSG.APP_EVENT) {
38
43
  if (msg.payload?.entityId) appRuntime.fireInteract(msg.payload.entityId, { id: clientId })
39
- if (msg.payload?.type === 'fire') {
40
- const shooter = playerManager.getPlayer(clientId)
41
- const pos = shooter?.state?.position || [0, 0, 0]
42
- const origin = [pos[0], pos[1] + 0.9, pos[2]]
43
- appRuntime.fireMessage('game', { ...msg.payload, shooterId: clientId, origin })
44
+ const eventData = { ...msg.payload, senderId: clientId }
45
+ for (const [entityId] of appRuntime.apps) {
46
+ appRuntime.fireMessage(entityId, eventData)
44
47
  }
45
48
  return
46
49
  }
@@ -50,10 +53,46 @@ export function createConnectionHandlers(ctx) {
50
53
  connections.send(clientId, MSG.DISCONNECT_REASON, { code: DISCONNECT_REASONS.INVALID_SESSION })
51
54
  return
52
55
  }
56
+ const oldId = session.playerId
57
+ const savedState = session.state || {}
58
+ const client = connections.getClient(clientId)
59
+ const transport = client?.transport
60
+ if (!transport) return
61
+ const playerConfig = ctx.currentWorldDef?.player || {}
62
+ const sp = savedState.position || [...ctx.worldSpawnPoint]
63
+ if (playerManager.getPlayer(oldId)) {
64
+ playerManager.removePlayer(oldId)
65
+ networkState.removePlayer(oldId)
66
+ physicsIntegration.removePlayerCollider(oldId)
67
+ lagCompensator.clearPlayerHistory(oldId)
68
+ connections.broadcast(MSG.PLAYER_LEAVE, { playerId: oldId })
69
+ }
70
+ if (clientId !== oldId && playerManager.getPlayer(clientId)) {
71
+ playerManager.removePlayer(clientId)
72
+ networkState.removePlayer(clientId)
73
+ physicsIntegration.removePlayerCollider(clientId)
74
+ lagCompensator.clearPlayerHistory(clientId)
75
+ connections.broadcast(MSG.PLAYER_LEAVE, { playerId: clientId })
76
+ }
77
+ connections.detachClient(clientId)
78
+ const newId = playerManager.addPlayer(transport, { position: sp, health: savedState.health ?? playerConfig.health ?? 100, velocity: savedState.velocity, rotation: savedState.rotation })
79
+ networkState.addPlayer(newId, { position: sp })
80
+ physicsIntegration.addPlayerCollider(newId, playerConfig.capsuleRadius || 0.4)
81
+ physicsIntegration.setPlayerPosition(newId, sp)
82
+ const reconnClient = connections.addClient(newId, transport)
83
+ reconnClient.sessionToken = msg.payload.sessionToken
84
+ sessions.update(msg.payload.sessionToken, { state: playerManager.getPlayer(newId).state })
85
+ connections.send(newId, MSG.RECONNECT_ACK, { playerId: newId, tick: tickSystem.currentTick, sessionToken: msg.payload.sessionToken })
86
+ if (ctx.currentWorldDef) connections.send(newId, MSG.WORLD_DEF, ctx.currentWorldDef)
87
+ const clientModules = appLoader.getClientModules()
88
+ for (const [appName, code] of Object.entries(clientModules)) {
89
+ connections.send(newId, MSG.APP_MODULE, { app: appName, code })
90
+ }
53
91
  const snap = networkState.getSnapshot()
54
92
  const ents = appRuntime.getSnapshot()
55
- connections.send(clientId, MSG.RECONNECT_ACK, { playerId: session.playerId, tick: tickSystem.currentTick, sessionToken: msg.payload.sessionToken })
56
- connections.send(clientId, MSG.STATE_RECOVERY, { snapshot: SnapshotEncoder.encode({ tick: snap.tick, timestamp: snap.timestamp, players: snap.players, entities: ents.entities }), tick: tickSystem.currentTick })
93
+ connections.send(newId, MSG.STATE_RECOVERY, { snapshot: SnapshotEncoder.encode({ tick: snap.tick, timestamp: snap.timestamp, players: snap.players, entities: ents.entities }), tick: tickSystem.currentTick })
94
+ for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId: newId })
95
+ emitter.emit('playerJoin', { id: newId, reconnected: true })
57
96
  return
58
97
  }
59
98
  emitter.emit('message', clientId, msg)
@@ -62,7 +101,7 @@ export function createConnectionHandlers(ctx) {
62
101
  connections.on('disconnect', (clientId, reason) => {
63
102
  const client = connections.getClient(clientId)
64
103
  if (client?.sessionToken) { const p = playerManager.getPlayer(clientId); if (p) sessions.update(client.sessionToken, { state: p.state }) }
65
- appRuntime.fireMessage('game', { type: 'player_leave', playerId: clientId })
104
+ for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_leave', playerId: clientId })
66
105
  physicsIntegration.removePlayerCollider(clientId)
67
106
  lagCompensator.clearPlayerHistory(clientId)
68
107
  inspector.removeClient(clientId)
@@ -1,32 +1,48 @@
1
- import { readFileSync, existsSync } from 'node:fs'
1
+ import { readFileSync, existsSync, statSync } from 'node:fs'
2
2
  import { join, extname } from 'node:path'
3
+ import { gzipSync } from 'node:zlib'
3
4
 
4
5
  const MIME_TYPES = {
5
6
  '.html': 'text/html', '.js': 'text/javascript', '.css': 'text/css',
6
- '.json': 'application/json', '.glb': 'model/gltf-binary', '.gltf': 'model/gltf+json',
7
+ '.json': 'application/json', '.glb': 'model/gltf-binary', '.gltf': 'model/gltf+json', '.vrm': 'model/gltf-binary',
7
8
  '.png': 'image/png', '.jpg': 'image/jpeg', '.webp': 'image/webp',
8
- '.svg': 'image/svg+xml', '.wasm': 'application/wasm'
9
+ '.svg': 'image/svg+xml', '.wasm': 'application/wasm', '.ico': 'image/x-icon'
9
10
  }
10
11
 
12
+ const GZIP_EXTENSIONS = new Set(['.glb', '.vrm', '.gltf', '.js', '.css', '.html', '.json'])
13
+
11
14
  export function createStaticHandler(dirs) {
12
15
  return (req, res) => {
13
16
  const url = req.url.split('?')[0]
17
+ if (url === '/favicon.ico') {
18
+ res.writeHead(204)
19
+ res.end()
20
+ return
21
+ }
14
22
  for (const { prefix, dir } of dirs) {
15
23
  if (!url.startsWith(prefix)) continue
16
24
  const relative = url === prefix ? '/index.html' : url.slice(prefix.length)
17
25
  const fp = join(dir, relative)
18
- if (existsSync(fp)) {
26
+ if (existsSync(fp) && statSync(fp).isFile()) {
19
27
  const ext = extname(fp)
20
28
  const headers = { 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream' }
21
29
  if (ext === '.js' || ext === '.html' || ext === '.css') {
22
30
  headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
31
+ } else if (ext === '.glb' || ext === '.vrm' || ext === '.gltf') {
32
+ headers['Cache-Control'] = 'public, max-age=86400, immutable'
33
+ }
34
+ let content = readFileSync(fp)
35
+ if (GZIP_EXTENSIONS.has(ext) && content.length > 100) {
36
+ content = gzipSync(content)
37
+ headers['Content-Encoding'] = 'gzip'
23
38
  }
39
+ headers['Content-Length'] = content.length
24
40
  res.writeHead(200, headers)
25
- res.end(readFileSync(fp))
41
+ res.end(content)
26
42
  return
27
43
  }
28
44
  }
29
- res.writeHead(404)
45
+ res.writeHead(404, { 'Cache-Control': 'no-store' })
30
46
  res.end('not found')
31
47
  }
32
48
  }
@@ -1,84 +1,151 @@
1
1
  import { MSG } from '../protocol/MessageTypes.js'
2
2
  import { SnapshotEncoder } from '../netcode/SnapshotEncoder.js'
3
- import { applyMovement, DEFAULT_MOVEMENT } from '../shared/movement.js'
3
+ import { applyMovement as _applyMovement, DEFAULT_MOVEMENT as _DEFAULT_MOVEMENT } from '../shared/movement.js'
4
+
5
+ const KEYFRAME_INTERVAL = 128
4
6
 
5
7
  export function createTickHandler(deps) {
6
8
  const {
7
9
  networkState, playerManager, physicsIntegration,
8
10
  lagCompensator, physics, appRuntime, connections,
9
- movement: m = {}, stageLoader, eventLog
11
+ movement: m = {}, stageLoader, eventLog, _movement
10
12
  } = deps
13
+ const applyMovement = _movement?.applyMovement || _applyMovement
14
+ const DEFAULT_MOVEMENT = _movement?.DEFAULT_MOVEMENT || _DEFAULT_MOVEMENT
11
15
  const movement = { ...DEFAULT_MOVEMENT, ...m }
12
- const collisionRestitution = m.collisionRestitution || 0.2
13
- const collisionDamping = m.collisionDamping || 0.25
16
+ const collisionRestitution = movement.collisionRestitution || 0.2
17
+ const collisionDamping = movement.collisionDamping || 0.25
14
18
  let snapshotSeq = 0
15
19
 
20
+ const playerEntityMaps = new Map()
21
+ let broadcastEntityMap = new Map()
22
+
23
+ let profileLog = 0
24
+ const separated = new Set()
16
25
  return function onTick(tick, dt) {
26
+ const t0 = performance.now()
17
27
  networkState.setTick(tick, Date.now())
18
- for (const player of playerManager.getConnectedPlayers()) {
28
+ const players = playerManager.getConnectedPlayers()
29
+ for (const player of players) {
19
30
  const inputs = playerManager.getInputs(player.id)
20
31
  const st = player.state
21
- let inp = null
22
32
 
23
33
  if (inputs.length > 0) {
24
- inp = inputs[inputs.length - 1].data
25
- if (inp) {
26
- const yaw = inp.yaw || 0
27
- st.rotation = [0, Math.sin(yaw / 2), 0, Math.cos(yaw / 2)]
28
- }
34
+ player.lastInput = inputs[inputs.length - 1].data
29
35
  playerManager.clearInputs(player.id)
30
36
  }
37
+ const inp = player.lastInput || null
38
+ if (inp) {
39
+ const yaw = inp.yaw || 0
40
+ st.rotation = [0, Math.sin(yaw / 2), 0, Math.cos(yaw / 2)]
41
+ st.crouch = inp.crouch ? 1 : 0
42
+ st.lookPitch = inp.pitch || 0
43
+ st.lookYaw = yaw
44
+ }
31
45
 
32
46
  applyMovement(st, inp, movement, dt)
47
+ if (inp) physicsIntegration.setCrouch(player.id, !!inp.crouch)
48
+ const wishedVx = st.velocity[0], wishedVz = st.velocity[2]
33
49
  const updated = physicsIntegration.updatePlayerPhysics(player.id, st, dt)
34
50
  st.position = updated.position
35
51
  st.velocity = updated.velocity
52
+ st.velocity[0] = wishedVx
53
+ st.velocity[2] = wishedVz
36
54
  st.onGround = updated.onGround
37
55
  lagCompensator.recordPlayerPosition(player.id, st.position, st.rotation, st.velocity, tick)
38
56
  networkState.updatePlayer(player.id, {
39
57
  position: st.position, rotation: st.rotation,
40
58
  velocity: st.velocity, onGround: st.onGround,
41
- health: st.health, inputSequence: player.inputSequence
59
+ health: st.health, inputSequence: player.inputSequence,
60
+ crouch: st.crouch || 0, lookPitch: st.lookPitch || 0, lookYaw: st.lookYaw || 0
42
61
  })
43
62
  }
44
- const players = playerManager.getConnectedPlayers()
63
+ const t1 = performance.now()
64
+ separated.clear()
45
65
  for (const player of players) {
46
66
  const collisions = physicsIntegration.checkCollisionWithOthers(player.id, players)
47
67
  for (const collision of collisions) {
68
+ const pairKey = player.id < collision.playerId ? `${player.id}-${collision.playerId}` : `${collision.playerId}-${player.id}`
69
+ if (separated.has(pairKey)) continue
70
+ separated.add(pairKey)
48
71
  const other = playerManager.getPlayer(collision.playerId)
49
72
  if (!other) continue
50
- const dx = collision.normal[0], dy = collision.normal[1], dz = collision.normal[2]
51
- const relVx = other.state.velocity[0] - player.state.velocity[0]
52
- const relVz = other.state.velocity[2] - player.state.velocity[2]
53
- const relDotNorm = relVx * dx + relVz * dz
54
- if (relDotNorm >= 0) continue
55
- const impulse = (1 + collisionRestitution) * relDotNorm * 0.5
56
- player.state.velocity[0] -= impulse * dx * collisionDamping
57
- player.state.velocity[2] -= impulse * dz * collisionDamping
58
- other.state.velocity[0] += impulse * dx * collisionDamping
59
- other.state.velocity[2] += impulse * dz * collisionDamping
73
+ const nx = collision.normal[0], nz = collision.normal[2]
74
+ const minDist = physicsIntegration.config.capsuleRadius * 2
75
+ const overlap = minDist - collision.distance
76
+ const halfPush = overlap * 0.5
77
+ const pushVel = Math.min(halfPush / dt, 3.0)
78
+ player.state.position[0] -= nx * halfPush
79
+ player.state.position[2] -= nz * halfPush
80
+ player.state.velocity[0] -= nx * pushVel
81
+ player.state.velocity[2] -= nz * pushVel
82
+ other.state.position[0] += nx * halfPush
83
+ other.state.position[2] += nz * halfPush
84
+ other.state.velocity[0] += nx * pushVel
85
+ other.state.velocity[2] += nz * pushVel
86
+ physicsIntegration.setPlayerPosition(player.id, player.state.position)
87
+ physicsIntegration.setPlayerPosition(other.id, other.state.position)
60
88
  }
61
89
  }
90
+ const t2 = performance.now()
62
91
  physics.step(dt)
92
+ const t3 = performance.now()
63
93
  appRuntime.tick(tick, dt)
64
- const playerSnap = networkState.getSnapshot()
65
- snapshotSeq++
66
- if (stageLoader && stageLoader.getActiveStage()) {
67
- for (const player of players) {
68
- const pos = player.state.position
69
- const entitySnap = appRuntime.getSnapshotForPlayer(pos, stageLoader.getActiveStage().spatial.relevanceRadius)
94
+ const t4 = performance.now()
95
+ if (players.length > 0) {
96
+ const playerSnap = networkState.getSnapshot()
97
+ snapshotSeq++
98
+ const isKeyframe = snapshotSeq % KEYFRAME_INTERVAL === 0
99
+ if (stageLoader && stageLoader.getActiveStage()) {
100
+ for (const player of players) {
101
+ const pos = player.state.position
102
+ const entitySnap = appRuntime.getSnapshotForPlayer(pos, stageLoader.getActiveStage().spatial.relevanceRadius)
103
+ const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
104
+ if (isKeyframe || !playerEntityMaps.has(player.id)) {
105
+ const encoded = SnapshotEncoder.encode(combined)
106
+ const { entityMap } = SnapshotEncoder.encodeDelta(combined, new Map())
107
+ playerEntityMaps.set(player.id, entityMap)
108
+ connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
109
+ } else {
110
+ const prevMap = playerEntityMaps.get(player.id)
111
+ const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap)
112
+ playerEntityMaps.set(player.id, entityMap)
113
+ connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
114
+ }
115
+ }
116
+ } else {
117
+ const entitySnap = appRuntime.getSnapshot()
70
118
  const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
71
- connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...SnapshotEncoder.encode(combined) })
119
+ if (isKeyframe || broadcastEntityMap.size === 0) {
120
+ const encoded = SnapshotEncoder.encode(combined)
121
+ const { entityMap } = SnapshotEncoder.encodeDelta(combined, new Map())
122
+ broadcastEntityMap = entityMap
123
+ connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
124
+ } else {
125
+ const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, broadcastEntityMap)
126
+ broadcastEntityMap = entityMap
127
+ connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
128
+ }
72
129
  }
73
- } else {
74
- const entitySnap = appRuntime.getSnapshot()
75
- const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
76
- connections.broadcast(MSG.SNAPSHOT, { seq: snapshotSeq, ...SnapshotEncoder.encode(combined) })
77
130
  }
131
+ for (const id of playerEntityMaps.keys()) {
132
+ if (!playerManager.getPlayer(id)) playerEntityMaps.delete(id)
133
+ }
134
+ const t5 = performance.now()
78
135
  try {
79
136
  appRuntime._drainReloadQueue()
80
137
  } catch (e) {
81
138
  console.error('[TickHandler] reload queue error:', e.message)
82
139
  }
140
+ profileLog++
141
+ if (profileLog % 1280 === 0) {
142
+ const total = t5 - t0
143
+ const mem = process.memoryUsage()
144
+ const heap = (mem.heapUsed / 1048576).toFixed(1)
145
+ const rss = (mem.rss / 1048576).toFixed(1)
146
+ const ext = (mem.external / 1048576).toFixed(1)
147
+ const ab = (mem.arrayBuffers / 1048576).toFixed(1)
148
+ try { console.log(`[tick-profile] tick:${tick} players:${players.length} total:${total.toFixed(2)}ms | mv:${(t1-t0).toFixed(2)} col:${(t2-t1).toFixed(2)} phys:${(t3-t2).toFixed(2)} app:${(t4-t3).toFixed(2)} snap:${(t5-t4).toFixed(2)} | heap:${heap}MB rss:${rss}MB ext:${ext}MB ab:${ab}MB`) } catch (_) {}
149
+ }
83
150
  }
84
151
  }
@@ -0,0 +1,28 @@
1
+ import { join, dirname, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { existsSync, mkdirSync, readdirSync, copyFileSync, statSync } from 'node:fs'
4
+
5
+ const SDK_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..')
6
+
7
+ function copyDir(src, dest) {
8
+ mkdirSync(dest, { recursive: true })
9
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
10
+ const s = join(src, entry.name)
11
+ const d = join(dest, entry.name)
12
+ if (entry.isDirectory()) copyDir(s, d)
13
+ else copyFileSync(s, d)
14
+ }
15
+ }
16
+
17
+ export async function scaffold() {
18
+ const cwd = process.cwd()
19
+ const localApps = resolve(cwd, 'apps')
20
+ if (existsSync(localApps)) {
21
+ console.log(`[scaffold] apps/ already exists at ${localApps}, skipping`)
22
+ return
23
+ }
24
+ const sdkApps = join(SDK_ROOT, 'apps')
25
+ copyDir(sdkApps, localApps)
26
+ console.log(`[scaffold] created apps/ at ${localApps}`)
27
+ console.log(`[scaffold] run 'spoint' to start the server`)
28
+ }