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
package/client/style.css CHANGED
@@ -9,3 +9,254 @@ canvas { display: block; width: 100%; height: 100vh; }
9
9
  #health-text { position: absolute; top: -20px; left: 50%; transform: translateX(-50%); font-size: 14px; color: #fff; text-shadow: 0 0 4px #000; }
10
10
  #info { position: absolute; top: 10px; left: 10px; font-size: 13px; line-height: 1.6; color: #0f0; text-shadow: 0 0 2px #000; }
11
11
  #click-prompt { position: absolute; top: 50%; left: 50%; transform: translate(-50%, 30px); font-size: 18px; color: rgba(255,255,255,0.7); text-shadow: 0 0 4px #000; z-index: 20; }
12
+
13
+ .loading-overlay {
14
+ position: fixed;
15
+ top: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ z-index: 9999;
24
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
25
+ }
26
+
27
+ .loading-container {
28
+ text-align: center;
29
+ color: white;
30
+ max-width: 400px;
31
+ width: 90%;
32
+ }
33
+
34
+ .loading-header h1 {
35
+ margin: 0 0 10px 0;
36
+ font-size: 2.5rem;
37
+ font-weight: 700;
38
+ letter-spacing: 2px;
39
+ background: linear-gradient(135deg, #00d4ff, #0099ff);
40
+ -webkit-background-clip: text;
41
+ -webkit-text-fill-color: transparent;
42
+ background-clip: text;
43
+ }
44
+
45
+ .loading-stage-text {
46
+ margin: 0;
47
+ font-size: 0.95rem;
48
+ opacity: 0.8;
49
+ color: #a0a0a0;
50
+ min-height: 1.5rem;
51
+ }
52
+
53
+ .loading-progress-wrapper {
54
+ margin: 30px 0;
55
+ position: relative;
56
+ }
57
+
58
+ .loading-progress-bar {
59
+ width: 100%;
60
+ height: 6px;
61
+ background: rgba(255, 255, 255, 0.1);
62
+ border-radius: 3px;
63
+ overflow: hidden;
64
+ }
65
+
66
+ .loading-progress-fill {
67
+ height: 100%;
68
+ background: linear-gradient(90deg, #00d4ff, #0099ff);
69
+ width: 0%;
70
+ transition: width 0.2s ease-out;
71
+ box-shadow: 0 0 10px rgba(0, 212, 255, 0.5);
72
+ }
73
+
74
+ .loading-percent {
75
+ position: absolute;
76
+ right: 0;
77
+ top: -24px;
78
+ font-size: 0.85rem;
79
+ font-weight: 600;
80
+ min-width: 40px;
81
+ }
82
+
83
+ .loading-details {
84
+ font-size: 0.75rem;
85
+ color: #808080;
86
+ margin-top: 15px;
87
+ min-height: 1rem;
88
+ }
89
+
90
+ .loading-details .loading-current,
91
+ .loading-details .loading-total {
92
+ color: #00d4ff;
93
+ font-weight: 600;
94
+ }
95
+
96
+ .loading-spinner {
97
+ display: flex;
98
+ justify-content: center;
99
+ gap: 8px;
100
+ margin-top: 30px;
101
+ height: 12px;
102
+ }
103
+
104
+ .spinner-dot {
105
+ width: 8px;
106
+ height: 8px;
107
+ border-radius: 50%;
108
+ background: #00d4ff;
109
+ opacity: 0.4;
110
+ animation: spin-bounce 1.2s infinite ease-in-out;
111
+ }
112
+
113
+ .spinner-dot:nth-child(1) {
114
+ animation-delay: -0.32s;
115
+ }
116
+
117
+ .spinner-dot:nth-child(2) {
118
+ animation-delay: -0.16s;
119
+ }
120
+
121
+ @keyframes spin-bounce {
122
+ 0%, 80%, 100% {
123
+ opacity: 0.4;
124
+ transform: scale(0.8);
125
+ }
126
+ 40% {
127
+ opacity: 1;
128
+ transform: scale(1);
129
+ }
130
+ }
131
+
132
+ .loading-overlay.fade-out {
133
+ animation: fade-out-animation 0.5s ease-out forwards;
134
+ }
135
+
136
+ @keyframes fade-out-animation {
137
+ from {
138
+ opacity: 1;
139
+ visibility: visible;
140
+ }
141
+ to {
142
+ opacity: 0;
143
+ visibility: hidden;
144
+ }
145
+ }
146
+
147
+ @media (max-width: 640px) {
148
+ .loading-header h1 {
149
+ font-size: 1.8rem;
150
+ }
151
+
152
+ .loading-stage-text {
153
+ font-size: 0.85rem;
154
+ }
155
+
156
+ .loading-details {
157
+ font-size: 0.7rem;
158
+ }
159
+ }
160
+
161
+ #mobile-controls {
162
+ position: fixed;
163
+ top: 0;
164
+ left: 0;
165
+ right: 0;
166
+ bottom: 0;
167
+ pointer-events: none;
168
+ z-index: 1000;
169
+ touch-action: none;
170
+ }
171
+
172
+ .mobile-joystick {
173
+ position: absolute;
174
+ border-radius: 50%;
175
+ background: rgba(255, 255, 255, 0.15);
176
+ border: 2px solid rgba(255, 255, 255, 0.3);
177
+ pointer-events: auto;
178
+ touch-action: none;
179
+ }
180
+
181
+ .mobile-joystick-knob {
182
+ position: absolute;
183
+ top: 50%;
184
+ left: 50%;
185
+ border-radius: 50%;
186
+ background: rgba(255, 255, 255, 0.5);
187
+ transform: translate(-50%, -50%);
188
+ touch-action: none;
189
+ }
190
+
191
+ .mobile-joystick-knob.active {
192
+ background: rgba(255, 255, 255, 0.7) !important;
193
+ }
194
+
195
+ .mobile-buttons {
196
+ position: absolute;
197
+ display: grid;
198
+ gap: 8px;
199
+ pointer-events: auto;
200
+ }
201
+
202
+ .mobile-button {
203
+ border-radius: 50%;
204
+ background: rgba(255, 255, 255, 0.2);
205
+ border: 2px solid rgba(255, 255, 255, 0.4);
206
+ display: flex;
207
+ align-items: center;
208
+ justify-content: center;
209
+ color: white;
210
+ font-weight: bold;
211
+ user-select: none;
212
+ -webkit-user-select: none;
213
+ touch-action: none;
214
+ }
215
+
216
+ .mobile-button.active {
217
+ background: rgba(255, 255, 255, 0.5) !important;
218
+ transform: scale(0.95);
219
+ }
220
+
221
+ .mobile-button-shoot {
222
+ background: rgba(255, 100, 100, 0.4);
223
+ }
224
+
225
+ .mobile-button-reload {
226
+ background: rgba(100, 100, 255, 0.4);
227
+ }
228
+
229
+ #ar-button {
230
+ position: fixed;
231
+ bottom: 20px;
232
+ left: 50%;
233
+ transform: translateX(-50%);
234
+ padding: 12px 24px;
235
+ background: rgba(0, 150, 0, 0.8);
236
+ color: white;
237
+ border: none;
238
+ border-radius: 8px;
239
+ font-size: 16px;
240
+ font-weight: bold;
241
+ cursor: pointer;
242
+ z-index: 1001;
243
+ touch-action: none;
244
+ }
245
+
246
+ @media (max-width: 480px) {
247
+ .mobile-joystick {
248
+ width: 100px !important;
249
+ height: 100px !important;
250
+ }
251
+
252
+ .mobile-joystick-knob {
253
+ width: 40px !important;
254
+ height: 40px !important;
255
+ }
256
+
257
+ .mobile-button {
258
+ width: 44px !important;
259
+ height: 44px !important;
260
+ font-size: 14px !important;
261
+ }
262
+ }
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.0",
3
+ "version": "0.1.10",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
8
- "spoint": "./server.js"
8
+ "spoint": "./server.js",
9
+ "spoint-create-app": "./bin/create-app.js"
9
10
  },
10
11
  "scripts": {
11
12
  "start": "node server.js"
@@ -13,9 +14,12 @@
13
14
  "files": [
14
15
  "src/",
15
16
  "apps/",
16
- "world/",
17
17
  "client/",
18
+ "!client/test-*.html",
19
+ "!client/vrm-test.html",
20
+ "bin/",
18
21
  "server.js",
22
+ "SKILL.md",
19
23
  "LICENSE",
20
24
  "README.md"
21
25
  ],
package/server.js CHANGED
@@ -1,3 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { boot } from './src/sdk/server.js'
3
- await boot()
3
+ import { scaffold } from './src/sdk/scaffold.js'
4
+
5
+ const cmd = process.argv[2]
6
+ if (cmd === 'scaffold') {
7
+ await scaffold()
8
+ } else {
9
+ await scaffold()
10
+ await boot()
11
+ }
@@ -70,7 +70,7 @@ export class AppContext {
70
70
  addTrimeshCollider: () => {
71
71
  ent.collider = { type: 'trimesh', model: ent.model }
72
72
  if (runtime._physics && ent.model) {
73
- const bodyId = runtime._physics.addStaticTrimesh(ent.model, 0)
73
+ const bodyId = runtime._physics.addStaticTrimesh(runtime.resolveAssetPath(ent.model), 0)
74
74
  ent._physicsBodyId = bodyId
75
75
  }
76
76
  },
@@ -1,4 +1,5 @@
1
1
  import { readdir, readFile, watch, access } from 'node:fs/promises'
2
+ import { existsSync } from 'node:fs'
2
3
  import { join, basename, extname, resolve } from 'node:path'
3
4
  import { pathToFileURL } from 'node:url'
4
5
 
@@ -10,33 +11,39 @@ const BLOCKED_PATTERNS = [
10
11
  export class AppLoader {
11
12
  constructor(runtime, config = {}) {
12
13
  this._runtime = runtime
13
- this._dir = config.dir || './apps'
14
+ this._dirs = config.dirs || [config.dir || './apps']
14
15
  this._watchers = new Map()
15
16
  this._loaded = new Map()
16
17
  this._onReloadCallback = null
17
18
  }
18
19
 
19
20
  async _resolvePath(name) {
20
- const flat = join(this._dir, `${name}.js`)
21
- try { await access(flat); return flat } catch {}
22
- const folder = join(this._dir, name, 'index.js')
23
- try { await access(folder); return folder } catch {}
21
+ for (const dir of this._dirs) {
22
+ const flat = join(dir, `${name}.js`)
23
+ try { await access(flat); return flat } catch {}
24
+ const folder = join(dir, name, 'index.js')
25
+ try { await access(folder); return folder } catch {}
26
+ }
24
27
  return null
25
28
  }
26
29
 
27
30
  async loadAll() {
28
- const entries = await readdir(this._dir, { withFileTypes: true }).catch(() => [])
31
+ const seen = new Set()
29
32
  const results = []
30
- for (const entry of entries) {
31
- let name = null
32
- if (entry.isFile() && entry.name.endsWith('.js')) {
33
- name = basename(entry.name, extname(entry.name))
34
- } else if (entry.isDirectory()) {
35
- try { await access(join(this._dir, entry.name, 'index.js')); name = entry.name } catch {}
36
- }
37
- if (name) {
38
- const loaded = await this.loadApp(name)
39
- if (loaded) results.push(name)
33
+ for (const dir of this._dirs) {
34
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => [])
35
+ for (const entry of entries) {
36
+ let name = null
37
+ if (entry.isFile() && entry.name.endsWith('.js')) {
38
+ name = basename(entry.name, extname(entry.name))
39
+ } else if (entry.isDirectory()) {
40
+ try { await access(join(dir, entry.name, 'index.js')); name = entry.name } catch {}
41
+ }
42
+ if (name && !seen.has(name)) {
43
+ seen.add(name)
44
+ const loaded = await this.loadApp(name)
45
+ if (loaded) results.push(name)
46
+ }
40
47
  }
41
48
  }
42
49
  return results
@@ -82,28 +89,34 @@ export class AppLoader {
82
89
  }
83
90
 
84
91
  async watchAll() {
85
- try {
86
- const ac = new AbortController()
87
- const watcher = watch(this._dir, { recursive: true, signal: ac.signal })
88
- this._watchers.set('__dir__', ac)
89
- ;(async () => {
90
- try {
91
- for await (const event of watcher) {
92
- if (!event.filename || !event.filename.endsWith('.js')) continue
93
- const parts = event.filename.replace(/\\/g, '/').split('/')
94
- const name = parts.length > 1
95
- ? parts[0]
96
- : basename(event.filename, extname(event.filename))
97
- await this._onFileChange(name)
98
- }
99
- } catch (e) {
100
- if (e.name !== 'AbortError') {
101
- console.error(`[AppLoader] watch error:`, e.message)
92
+ for (const dir of this._dirs) {
93
+ if (!existsSync(dir)) {
94
+ console.debug(`[AppLoader] skipping watch for missing directory: ${dir}`)
95
+ continue
96
+ }
97
+ try {
98
+ const ac = new AbortController()
99
+ const watcher = watch(dir, { recursive: true, signal: ac.signal })
100
+ this._watchers.set(dir, ac)
101
+ ;(async () => {
102
+ try {
103
+ for await (const event of watcher) {
104
+ if (!event.filename || !event.filename.endsWith('.js')) continue
105
+ const parts = event.filename.replace(/\\/g, '/').split('/')
106
+ const name = parts.length > 1
107
+ ? parts[0]
108
+ : basename(event.filename, extname(event.filename))
109
+ await this._onFileChange(name)
110
+ }
111
+ } catch (e) {
112
+ if (e.name !== 'AbortError') {
113
+ console.error(`[AppLoader] watch error:`, e.message)
114
+ }
102
115
  }
103
- }
104
- })()
105
- } catch (e) {
106
- console.error(`[AppLoader] watchAll error:`, e.message)
116
+ })()
117
+ } catch (e) {
118
+ console.error(`[AppLoader] watchAll error:`, e.message)
119
+ }
107
120
  }
108
121
  }
109
122
 
@@ -3,6 +3,8 @@ import { HotReloadQueue } from './HotReloadQueue.js'
3
3
  import { EventBus } from './EventBus.js'
4
4
  import { mulQuat, rotVec } from '../math.js'
5
5
  import { MSG } from '../protocol/MessageTypes.js'
6
+ import { existsSync } from 'node:fs'
7
+ import { resolve } from 'node:path'
6
8
 
7
9
  export class AppRuntime {
8
10
  constructor(c = {}) {
@@ -16,6 +18,7 @@ export class AppRuntime {
16
18
  this._eventBus = c.eventBus || new EventBus()
17
19
  this._eventLog = c.eventLog || null
18
20
  this._storage = c.storage || null
21
+ this._sdkRoot = c.sdkRoot || null
19
22
  this._eventBus.on('*', (event) => {
20
23
  if (event.channel.startsWith('system.')) return
21
24
  this._log('bus_event', { channel: event.channel, data: event.data }, event.meta)
@@ -26,6 +29,20 @@ export class AppRuntime {
26
29
  })
27
30
  }
28
31
 
32
+ resolveAssetPath(p) {
33
+ if (!p) return p
34
+ const local = resolve(p)
35
+ if (existsSync(local)) return local
36
+ if (this._sdkRoot) {
37
+ const sdk = resolve(this._sdkRoot, p)
38
+ if (existsSync(sdk)) {
39
+ console.debug(`[SDK-DEFAULT] using bundled asset: ${p}`)
40
+ return sdk
41
+ }
42
+ }
43
+ return local
44
+ }
45
+
29
46
  registerApp(name, appDef) { this._appDefs.set(name, appDef) }
30
47
 
31
48
  spawnEntity(id, config = {}) {
@@ -47,7 +64,7 @@ export class AppRuntime {
47
64
  }
48
65
  if (config.autoTrimesh && entity.model && this._physics) {
49
66
  entity.collider = { type: 'trimesh', model: entity.model }
50
- entity._physicsBodyId = this._physics.addStaticTrimesh(entity.model, 0)
67
+ entity._physicsBodyId = this._physics.addStaticTrimesh(this.resolveAssetPath(entity.model), 0)
51
68
  }
52
69
  if (config.app) this._attachApp(entityId, config.app)
53
70
  this._spatialInsert(entity)
@@ -128,7 +145,7 @@ export class AppRuntime {
128
145
  return { tick: this.currentTick, timestamp: Date.now(), entities }
129
146
  }
130
147
 
131
- queryEntities(f) { return f ? Array.from(this.entities.values()).filter(f) : Array.from(this.entities.values()) }
148
+ queryEntities(f) { const r = []; for (const e of this.entities.values()) { if (!f || f(e)) r.push(e) } return r }
132
149
  getEntity(id) { return this.entities.get(id) || null }
133
150
  fireEvent(eid, en, ...a) { const ad = this.apps.get(eid), c = this.contexts.get(eid); if (!ad || !c) return; this._log('app_event', { entityId: eid, event: en, args: a }, { sourceEntity: eid }); const s = ad.server || ad; if (s[en]) this._safeCall(s, en, [c, ...a], `${en}(${eid})`) }
134
151
  fireInteract(eid, p) { this.fireEvent(eid, 'onInteract', p) }
@@ -149,12 +166,19 @@ export class AppRuntime {
149
166
  }
150
167
 
151
168
  _tickCollisions() {
152
- const c = Array.from(this.entities.values()).filter(e => e.collider && this.apps.has(e.id))
153
- for (let i = 0; i < c.length; i++) for (let j = i + 1; j < c.length; j++) {
154
- const a = c[i], b = c[j], d = Math.hypot(b.position[0]-a.position[0], b.position[1]-a.position[1], b.position[2]-a.position[2])
155
- if (d < this._colR(a.collider) + this._colR(b.collider)) {
156
- this.fireEvent(a.id, 'onCollision', { id: b.id, position: b.position, velocity: b.velocity })
157
- this.fireEvent(b.id, 'onCollision', { id: a.id, position: a.position, velocity: a.velocity })
169
+ const c = []
170
+ for (const e of this.entities.values()) {
171
+ if (e.collider && this.apps.has(e.id)) { e._cachedColR = this._colR(e.collider); c.push(e) }
172
+ }
173
+ for (let i = 0; i < c.length; i++) {
174
+ const a = c[i], ar = a._cachedColR, ax = a.position[0], ay = a.position[1], az = a.position[2]
175
+ for (let j = i + 1; j < c.length; j++) {
176
+ const b = c[j], dx = b.position[0]-ax, dy = b.position[1]-ay, dz = b.position[2]-az
177
+ const rr = ar + b._cachedColR
178
+ if (dx*dx+dy*dy+dz*dz < rr*rr) {
179
+ this.fireEvent(a.id, 'onCollision', { id: b.id, position: b.position, velocity: b.velocity })
180
+ this.fireEvent(b.id, 'onCollision', { id: a.id, position: a.position, velocity: a.velocity })
181
+ }
158
182
  }
159
183
  }
160
184
  }