spoint 0.1.82 → 0.1.84

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 (139) hide show
  1. package/apps/maps/aim_kosova_ak47.glb +0 -0
  2. package/apps/maps/aim_kosova_battle.glb +0 -0
  3. package/apps/maps/aim_kosova_famas.glb +0 -0
  4. package/apps/maps/aim_sillos.glb +0 -0
  5. package/apps/maps/awp_india_ks.glb +0 -0
  6. package/apps/maps/awp_kosova.glb +0 -0
  7. package/apps/maps/awp_kosova_battle.glb +0 -0
  8. package/apps/maps/awp_kosovo_trainstation.glb +0 -0
  9. package/apps/maps/de_dolc.glb +0 -0
  10. package/apps/maps/de_dust2_kosovo.glb +0 -0
  11. package/apps/maps/de_gash.glb +0 -0
  12. package/apps/maps/de_kosovo.glb +0 -0
  13. package/apps/maps/de_kps.glb +0 -0
  14. package/apps/maps/de_pub2_r3.glb +0 -0
  15. package/apps/maps/deathrun_kosova.glb +0 -0
  16. package/apps/maps/fy_osama_house.glb +0 -0
  17. package/apps/prop-dynamic/index.js +13 -0
  18. package/apps/props/car1.glb +0 -0
  19. package/apps/props/car2.glb +0 -0
  20. package/apps/props/dynamic/3dPrinter1.glb +0 -0
  21. package/apps/props/dynamic/3dPrinter2.glb +0 -0
  22. package/apps/props/dynamic/AirConditioner1.glb +0 -0
  23. package/apps/props/dynamic/AirConditioner2.glb +0 -0
  24. package/apps/props/dynamic/AirConditioner3.glb +0 -0
  25. package/apps/props/dynamic/AirConditioner4.glb +0 -0
  26. package/apps/props/dynamic/ArmourToolBox1.glb +0 -0
  27. package/apps/props/dynamic/ArmourToolBox2.glb +0 -0
  28. package/apps/props/dynamic/ArmourToolBox3.glb +0 -0
  29. package/apps/props/dynamic/ArmourToolMan.glb +0 -0
  30. package/apps/props/dynamic/Atm1.glb +0 -0
  31. package/apps/props/dynamic/Atm2.glb +0 -0
  32. package/apps/props/dynamic/Atm3.glb +0 -0
  33. package/apps/props/dynamic/Atm4.glb +0 -0
  34. package/apps/props/dynamic/Ball1.glb +0 -0
  35. package/apps/props/dynamic/BeerBottle1.glb +0 -0
  36. package/apps/props/dynamic/BeerBottle2.glb +0 -0
  37. package/apps/props/dynamic/BeerBottle3.glb +0 -0
  38. package/apps/props/dynamic/BeerBottle4.glb +0 -0
  39. package/apps/props/dynamic/BreakRoomChair1.glb +0 -0
  40. package/apps/props/dynamic/BreakRoomChair2.glb +0 -0
  41. package/apps/props/dynamic/BreakRoomChair3.glb +0 -0
  42. package/apps/props/dynamic/BreakRoomChair4.glb +0 -0
  43. package/apps/props/dynamic/BreakRoomCouch1.glb +0 -0
  44. package/apps/props/dynamic/BreakRoomCouch2.glb +0 -0
  45. package/apps/props/dynamic/BreakRoomCouch3.glb +0 -0
  46. package/apps/props/dynamic/BreakRoomCouch4.glb +0 -0
  47. package/apps/props/dynamic/BreakRoomCouch5.glb +0 -0
  48. package/apps/props/dynamic/BreakRoomCouch6.glb +0 -0
  49. package/apps/props/dynamic/BreakRoomCouch7.glb +0 -0
  50. package/apps/props/dynamic/BreakRoomTable1.glb +0 -0
  51. package/apps/props/dynamic/BreakRoomTable2.glb +0 -0
  52. package/apps/props/dynamic/BrokenBeerBottles1.glb +0 -0
  53. package/apps/props/dynamic/BrokenBeerBottles2.glb +0 -0
  54. package/apps/props/dynamic/BrokenOfficeChair1.glb +0 -0
  55. package/apps/props/dynamic/BrokenOfficeChair2.glb +0 -0
  56. package/apps/props/dynamic/BrokenWaterCooler1.glb +0 -0
  57. package/apps/props/dynamic/BrokenWaterCooler2.glb +0 -0
  58. package/apps/props/dynamic/CanMan1.glb +0 -0
  59. package/apps/props/dynamic/CanMan2.glb +0 -0
  60. package/apps/props/dynamic/CanMan3.glb +0 -0
  61. package/apps/props/dynamic/CashRegister1.glb +0 -0
  62. package/apps/props/dynamic/CashRegister2.glb +0 -0
  63. package/apps/props/dynamic/CashRegister3.glb +0 -0
  64. package/apps/props/dynamic/ComfyChairs1.glb +0 -0
  65. package/apps/props/dynamic/CrushedGarbageCan1.glb +0 -0
  66. package/apps/props/dynamic/CrushedGarbageCan2.glb +0 -0
  67. package/apps/props/dynamic/CrushedGarbageCan3.glb +0 -0
  68. package/apps/props/dynamic/CrushedGarbageCan4.glb +0 -0
  69. package/apps/props/dynamic/DinnerTable1.glb +0 -0
  70. package/apps/props/dynamic/DinnerTable2.glb +0 -0
  71. package/apps/props/dynamic/DinnerTable3.glb +0 -0
  72. package/apps/props/dynamic/dj_mixer_baeeec4e_v1.glb +0 -0
  73. package/apps/props/dynamic/dj_mixer_baeeec4e_v2.glb +0 -0
  74. package/apps/props/dynamic/dj_mixer_baeeec4e_v3.glb +0 -0
  75. package/apps/props/dynamic/dj_mixer_baeeec4e_v4.glb +0 -0
  76. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v1.glb +0 -0
  77. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v2.glb +0 -0
  78. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v3.glb +0 -0
  79. package/apps/props/dynamic/fancy_reception_desk_58fde71d_v4.glb +0 -0
  80. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v1.glb +0 -0
  81. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v2.glb +0 -0
  82. package/apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v3.glb +0 -0
  83. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v1.glb +0 -0
  84. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v2.glb +0 -0
  85. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v3.glb +0 -0
  86. package/apps/props/dynamic/hi-fi_sound_system_2a2cc620_v4.glb +0 -0
  87. package/apps/props/dynamic/index.js +21 -0
  88. package/apps/props/dynamic/industrial_pipe_cb740c0c_v1.glb +0 -0
  89. package/apps/props/dynamic/industrial_pipe_cb740c0c_v2.glb +0 -0
  90. package/apps/props/dynamic/industrial_pipe_cb740c0c_v3.glb +0 -0
  91. package/apps/props/dynamic/industrial_pipe_cb740c0c_v4.glb +0 -0
  92. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v1.glb +0 -0
  93. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v2.glb +0 -0
  94. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v3.glb +0 -0
  95. package/apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v4.glb +0 -0
  96. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v1.glb +0 -0
  97. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v2.glb +0 -0
  98. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v3.glb +0 -0
  99. package/apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v4.glb +0 -0
  100. package/apps/props/dynamic/night_club_speakers_9155e359_v1.glb +0 -0
  101. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v1.glb +0 -0
  102. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v2.glb +0 -0
  103. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v3.glb +0 -0
  104. package/apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v4.glb +0 -0
  105. package/apps/props/dynamic/server_rack_03b09d1f_v1.glb +0 -0
  106. package/apps/props/dynamic/server_rack_03b09d1f_v2.glb +0 -0
  107. package/apps/props/dynamic/server_rack_03b09d1f_v3.glb +0 -0
  108. package/apps/props/dynamic/server_rack_03b09d1f_v4.glb +0 -0
  109. package/apps/props/dynamic/server_rack_c2999a18_v1.glb +0 -0
  110. package/apps/props/dynamic/server_rack_c2999a18_v2.glb +0 -0
  111. package/apps/props/dynamic/server_rack_c2999a18_v3.glb +0 -0
  112. package/apps/props/dynamic/server_rack_c2999a18_v4.glb +0 -0
  113. package/apps/props/dynamic/shop_counter_f668a712_v1.glb +0 -0
  114. package/apps/props/dynamic/warehouse_crate_6e8a0927_v1.glb +0 -0
  115. package/apps/props/dynamic/warehouse_crate_6e8a0927_v2.glb +0 -0
  116. package/apps/props/dynamic/warehouse_crate_6e8a0927_v4.glb +0 -0
  117. package/apps/props/dynamic/water_tank_c27c18f7_v1.glb +0 -0
  118. package/apps/props/dynamic/water_tank_c27c18f7_v2.glb +0 -0
  119. package/apps/props/dynamic/water_tank_c27c18f7_v3.glb +0 -0
  120. package/apps/props/dynamic/water_tank_c27c18f7_v4.glb +0 -0
  121. package/apps/props/fountain.glb +0 -0
  122. package/apps/props/index.js +24 -0
  123. package/apps/props/rifle.glb +0 -0
  124. package/apps/props/rock1.glb +0 -0
  125. package/apps/props/rock2.glb +0 -0
  126. package/apps/tps-game/schwust.glb +0 -0
  127. package/apps/world/index.js +138 -3
  128. package/client/app.js +72 -5
  129. package/package.json +1 -1
  130. package/src/apps/AppRuntime.js +31 -3
  131. package/src/netcode/SnapshotEncoder.js +6 -2
  132. package/src/physics/GLBLoader.js +6 -0
  133. package/src/physics/World.js +4 -0
  134. package/src/sdk/BotHarness.js +5 -5
  135. package/src/sdk/ServerAPI.js +3 -1
  136. package/src/sdk/ServerHandlers.js +7 -3
  137. package/src/sdk/TickHandler.js +13 -9
  138. package/src/sdk/server.js +2 -1
  139. package/src/stage/Stage.js +1 -4
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ export default {
2
+ server: {
3
+ async setup(ctx) {
4
+ ctx.physics.setDynamic(true)
5
+ ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
6
+ }
7
+ },
8
+ client: {
9
+ render(ctx) {
10
+ return { position: ctx.entity.position, rotation: ctx.entity.rotation, scale: ctx.entity.scale, model: ctx.entity.model }
11
+ }
12
+ }
13
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,21 @@
1
+ export default {
2
+ server: {
3
+ async setup(ctx) {
4
+ ctx.physics.setDynamic(true)
5
+ if (ctx.entity.model) {
6
+ try {
7
+ await ctx.physics.addConvexFromModel(0)
8
+ } catch (e) {
9
+ ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
10
+ }
11
+ } else {
12
+ ctx.physics.addBoxCollider(0.5, 0.5, 0.5)
13
+ }
14
+ }
15
+ },
16
+ client: {
17
+ render(ctx) {
18
+ return { position: ctx.entity.position, rotation: ctx.entity.rotation, scale: ctx.entity.scale, model: ctx.entity.model }
19
+ }
20
+ }
21
+ }
Binary file
@@ -0,0 +1,24 @@
1
+ export default {
2
+ server: {
3
+ async setup(ctx) {
4
+ ctx.physics.setStatic(true)
5
+ if (ctx.entity.model) {
6
+ try {
7
+ await ctx.physics.addConvexFromModel(0)
8
+ } catch (e) {
9
+ ctx.physics.addBoxCollider(1, 1, 1)
10
+ }
11
+ }
12
+ }
13
+ },
14
+ client: {
15
+ render(ctx) {
16
+ return {
17
+ position: ctx.entity.position,
18
+ rotation: ctx.entity.rotation,
19
+ scale: ctx.entity.scale,
20
+ model: ctx.entity.model
21
+ }
22
+ }
23
+ }
24
+ }
Binary file
Binary file
Binary file
Binary file
@@ -2,6 +2,7 @@ export default {
2
2
  port: 3001,
3
3
  tickRate: 128,
4
4
  gravity: [0, -9.81, 0],
5
+ relevanceRadius: 150,
5
6
  movement: {
6
7
  maxSpeed: 4.0,
7
8
  groundAccel: 10.0,
@@ -58,8 +59,142 @@ export default {
58
59
  fadeTime: 0.15
59
60
  },
60
61
  entities: [
61
- { id: 'environment', model: './apps/tps-game/schwust.glb', position: [0, 0, 0], app: 'environment' }
62
+ { id: 'env-schwust', model: './apps/tps-game/schwust.glb', position: [0, 0, 0], app: 'environment' },
63
+ { id: 'env-kosova', model: './apps/maps/aim_kosova_ak47.glb', position: [200, 0, 0], app: 'environment' },
64
+ { id: 'env-sillos', model: './apps/maps/aim_sillos.glb', position: [400, 0, 0], app: 'environment' },
65
+ { id: 'env-dust2', model: './apps/maps/de_dust2_kosovo.glb', position: [600, 0, 0], app: 'environment' },
66
+ { id: 'env-gash', model: './apps/maps/de_gash.glb', position: [800, 0, 0], app: 'environment' },
67
+
68
+ { id: 'prop-rock1-a', model: './apps/props/rock1.glb', position: [-10, 5, -10], app: 'props' },
69
+ { id: 'prop-rock1-b', model: './apps/props/rock1.glb', position: [10, 5, 10], app: 'props' },
70
+ { id: 'prop-rock2-a', model: './apps/props/rock2.glb', position: [-15, 5, 5], app: 'props' },
71
+ { id: 'prop-car1-a', model: './apps/props/car1.glb', position: [-5, 5, -20], app: 'props' },
72
+ { id: 'prop-car2-a', model: './apps/props/car2.glb', position: [15, 5, -15], app: 'props' },
73
+ { id: 'prop-fountain', model: './apps/props/fountain.glb',position: [0, 5, 15], app: 'props' },
74
+
75
+ { id: 'prop-rock1-c', model: './apps/props/rock1.glb', position: [190, 5, -10], app: 'props' },
76
+ { id: 'prop-rock2-b', model: './apps/props/rock2.glb', position: [210, 5, 10], app: 'props' },
77
+ { id: 'prop-car1-b', model: './apps/props/car1.glb', position: [205, 5, -15], app: 'props' },
78
+
79
+ { id: 'prop-rock1-d', model: './apps/props/rock1.glb', position: [390, 5, -10], app: 'props' },
80
+ { id: 'prop-car2-b', model: './apps/props/car2.glb', position: [415, 5, 10], app: 'props' },
81
+
82
+ { id: 'prop-rock2-c', model: './apps/props/rock2.glb', position: [590, 5, -10], app: 'props' },
83
+ { id: 'prop-car1-c', model: './apps/props/car1.glb', position: [610, 5, 5], app: 'props' },
84
+
85
+ { id: 'prop-rock1-e', model: './apps/props/rock1.glb', position: [790, 5, -10], app: 'props' },
86
+ { id: 'prop-car2-c', model: './apps/props/car2.glb', position: [810, 5, 5], app: 'props' },
87
+
88
+ { id: 'dyn-ground', app: 'box-static', position: [66, -1, 236], config: { hx: 80, hy: 1, hz: 80, color: 0x888888 } },
89
+ { id: 'dyn-0', model: './apps/props/dynamic/3dPrinter1.glb', position: [30, 30, 200], app: 'prop-dynamic' },
90
+ { id: 'dyn-1', model: './apps/props/dynamic/3dPrinter2.glb', position: [38, 30, 200], app: 'prop-dynamic' },
91
+ { id: 'dyn-2', model: './apps/props/dynamic/AirConditioner1.glb', position: [46, 30, 200], app: 'prop-dynamic' },
92
+ { id: 'dyn-3', model: './apps/props/dynamic/AirConditioner2.glb', position: [54, 30, 200], app: 'prop-dynamic' },
93
+ { id: 'dyn-4', model: './apps/props/dynamic/AirConditioner3.glb', position: [62, 30, 200], app: 'prop-dynamic' },
94
+ { id: 'dyn-5', model: './apps/props/dynamic/AirConditioner4.glb', position: [70, 30, 200], app: 'prop-dynamic' },
95
+ { id: 'dyn-6', model: './apps/props/dynamic/ArmourToolBox1.glb', position: [78, 30, 200], app: 'prop-dynamic' },
96
+ { id: 'dyn-7', model: './apps/props/dynamic/ArmourToolBox2.glb', position: [86, 30, 200], app: 'prop-dynamic' },
97
+ { id: 'dyn-8', model: './apps/props/dynamic/ArmourToolBox3.glb', position: [94, 30, 200], app: 'prop-dynamic' },
98
+ { id: 'dyn-9', model: './apps/props/dynamic/ArmourToolMan.glb', position: [102, 30, 200], app: 'prop-dynamic' },
99
+ { id: 'dyn-10', model: './apps/props/dynamic/Atm1.glb', position: [30, 33, 208], app: 'prop-dynamic' },
100
+ { id: 'dyn-11', model: './apps/props/dynamic/Atm2.glb', position: [38, 33, 208], app: 'prop-dynamic' },
101
+ { id: 'dyn-12', model: './apps/props/dynamic/Atm3.glb', position: [46, 33, 208], app: 'prop-dynamic' },
102
+ { id: 'dyn-13', model: './apps/props/dynamic/Atm4.glb', position: [54, 33, 208], app: 'prop-dynamic' },
103
+ { id: 'dyn-14', model: './apps/props/dynamic/Ball1.glb', position: [62, 33, 208], app: 'prop-dynamic' },
104
+ { id: 'dyn-15', model: './apps/props/dynamic/BeerBottle1.glb', position: [70, 33, 208], app: 'prop-dynamic' },
105
+ { id: 'dyn-16', model: './apps/props/dynamic/BeerBottle2.glb', position: [78, 33, 208], app: 'prop-dynamic' },
106
+ { id: 'dyn-17', model: './apps/props/dynamic/BeerBottle3.glb', position: [86, 33, 208], app: 'prop-dynamic' },
107
+ { id: 'dyn-18', model: './apps/props/dynamic/BeerBottle4.glb', position: [94, 33, 208], app: 'prop-dynamic' },
108
+ { id: 'dyn-19', model: './apps/props/dynamic/BreakRoomChair1.glb', position: [102, 33, 208], app: 'prop-dynamic' },
109
+ { id: 'dyn-20', model: './apps/props/dynamic/BreakRoomChair2.glb', position: [30, 36, 216], app: 'prop-dynamic' },
110
+ { id: 'dyn-21', model: './apps/props/dynamic/BreakRoomChair3.glb', position: [38, 36, 216], app: 'prop-dynamic' },
111
+ { id: 'dyn-22', model: './apps/props/dynamic/BreakRoomChair4.glb', position: [46, 36, 216], app: 'prop-dynamic' },
112
+ { id: 'dyn-23', model: './apps/props/dynamic/BreakRoomCouch1.glb', position: [54, 36, 216], app: 'prop-dynamic' },
113
+ { id: 'dyn-24', model: './apps/props/dynamic/BreakRoomCouch2.glb', position: [62, 36, 216], app: 'prop-dynamic' },
114
+ { id: 'dyn-25', model: './apps/props/dynamic/BreakRoomCouch3.glb', position: [70, 36, 216], app: 'prop-dynamic' },
115
+ { id: 'dyn-26', model: './apps/props/dynamic/BreakRoomCouch4.glb', position: [78, 36, 216], app: 'prop-dynamic' },
116
+ { id: 'dyn-27', model: './apps/props/dynamic/BreakRoomCouch5.glb', position: [86, 36, 216], app: 'prop-dynamic' },
117
+ { id: 'dyn-28', model: './apps/props/dynamic/BreakRoomCouch6.glb', position: [94, 36, 216], app: 'prop-dynamic' },
118
+ { id: 'dyn-29', model: './apps/props/dynamic/BreakRoomCouch7.glb', position: [102, 36, 216], app: 'prop-dynamic' },
119
+ { id: 'dyn-30', model: './apps/props/dynamic/BreakRoomTable1.glb', position: [30, 39, 224], app: 'prop-dynamic' },
120
+ { id: 'dyn-31', model: './apps/props/dynamic/BreakRoomTable2.glb', position: [38, 39, 224], app: 'prop-dynamic' },
121
+ { id: 'dyn-32', model: './apps/props/dynamic/BrokenBeerBottles1.glb', position: [46, 39, 224], app: 'prop-dynamic' },
122
+ { id: 'dyn-33', model: './apps/props/dynamic/BrokenBeerBottles2.glb', position: [54, 39, 224], app: 'prop-dynamic' },
123
+ { id: 'dyn-34', model: './apps/props/dynamic/BrokenOfficeChair1.glb', position: [62, 39, 224], app: 'prop-dynamic' },
124
+ { id: 'dyn-35', model: './apps/props/dynamic/BrokenOfficeChair2.glb', position: [70, 39, 224], app: 'prop-dynamic' },
125
+ { id: 'dyn-36', model: './apps/props/dynamic/BrokenWaterCooler1.glb', position: [78, 39, 224], app: 'prop-dynamic' },
126
+ { id: 'dyn-37', model: './apps/props/dynamic/BrokenWaterCooler2.glb', position: [86, 39, 224], app: 'prop-dynamic' },
127
+ { id: 'dyn-38', model: './apps/props/dynamic/CanMan1.glb', position: [94, 39, 224], app: 'prop-dynamic' },
128
+ { id: 'dyn-39', model: './apps/props/dynamic/CanMan2.glb', position: [102, 39, 224], app: 'prop-dynamic' },
129
+ { id: 'dyn-40', model: './apps/props/dynamic/CanMan3.glb', position: [30, 42, 232], app: 'prop-dynamic' },
130
+ { id: 'dyn-41', model: './apps/props/dynamic/CashRegister1.glb', position: [38, 42, 232], app: 'prop-dynamic' },
131
+ { id: 'dyn-42', model: './apps/props/dynamic/CashRegister2.glb', position: [46, 42, 232], app: 'prop-dynamic' },
132
+ { id: 'dyn-43', model: './apps/props/dynamic/CashRegister3.glb', position: [54, 42, 232], app: 'prop-dynamic' },
133
+ { id: 'dyn-44', model: './apps/props/dynamic/ComfyChairs1.glb', position: [62, 42, 232], app: 'prop-dynamic' },
134
+ { id: 'dyn-45', model: './apps/props/dynamic/CrushedGarbageCan1.glb', position: [70, 42, 232], app: 'prop-dynamic' },
135
+ { id: 'dyn-46', model: './apps/props/dynamic/CrushedGarbageCan2.glb', position: [78, 42, 232], app: 'prop-dynamic' },
136
+ { id: 'dyn-47', model: './apps/props/dynamic/CrushedGarbageCan3.glb', position: [86, 42, 232], app: 'prop-dynamic' },
137
+ { id: 'dyn-48', model: './apps/props/dynamic/CrushedGarbageCan4.glb', position: [94, 42, 232], app: 'prop-dynamic' },
138
+ { id: 'dyn-49', model: './apps/props/dynamic/DinnerTable1.glb', position: [102, 42, 232], app: 'prop-dynamic' },
139
+ { id: 'dyn-50', model: './apps/props/dynamic/DinnerTable2.glb', position: [30, 45, 240], app: 'prop-dynamic' },
140
+ { id: 'dyn-51', model: './apps/props/dynamic/DinnerTable3.glb', position: [38, 45, 240], app: 'prop-dynamic' },
141
+ { id: 'dyn-52', model: './apps/props/dynamic/dj_mixer_baeeec4e_v1.glb', position: [46, 45, 240], app: 'prop-dynamic' },
142
+ { id: 'dyn-53', model: './apps/props/dynamic/dj_mixer_baeeec4e_v2.glb', position: [54, 45, 240], app: 'prop-dynamic' },
143
+ { id: 'dyn-54', model: './apps/props/dynamic/dj_mixer_baeeec4e_v3.glb', position: [62, 45, 240], app: 'prop-dynamic' },
144
+ { id: 'dyn-55', model: './apps/props/dynamic/dj_mixer_baeeec4e_v4.glb', position: [70, 45, 240], app: 'prop-dynamic' },
145
+ { id: 'dyn-56', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v1.glb', position: [78, 45, 240], app: 'prop-dynamic' },
146
+ { id: 'dyn-57', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v2.glb', position: [86, 45, 240], app: 'prop-dynamic' },
147
+ { id: 'dyn-58', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v3.glb', position: [94, 45, 240], app: 'prop-dynamic' },
148
+ { id: 'dyn-59', model: './apps/props/dynamic/fancy_reception_desk_58fde71d_v4.glb', position: [102, 45, 240], app: 'prop-dynamic' },
149
+ { id: 'dyn-60', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v1.glb', position: [30, 48, 248], app: 'prop-dynamic' },
150
+ { id: 'dyn-61', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v2.glb', position: [38, 48, 248], app: 'prop-dynamic' },
151
+ { id: 'dyn-62', model: './apps/props/dynamic/heavy_machinery__crane_from_junk_yard_with_magnet_for_lifting_cars_db884752_v3.glb', position: [46, 48, 248], app: 'prop-dynamic' },
152
+ { id: 'dyn-63', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v1.glb', position: [54, 48, 248], app: 'prop-dynamic' },
153
+ { id: 'dyn-64', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v2.glb', position: [62, 48, 248], app: 'prop-dynamic' },
154
+ { id: 'dyn-65', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v3.glb', position: [70, 48, 248], app: 'prop-dynamic' },
155
+ { id: 'dyn-66', model: './apps/props/dynamic/hi-fi_sound_system_2a2cc620_v4.glb', position: [78, 48, 248], app: 'prop-dynamic' },
156
+ { id: 'dyn-67', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v1.glb', position: [86, 48, 248], app: 'prop-dynamic' },
157
+ { id: 'dyn-68', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v2.glb', position: [94, 48, 248], app: 'prop-dynamic' },
158
+ { id: 'dyn-69', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v3.glb', position: [102, 48, 248], app: 'prop-dynamic' },
159
+ { id: 'dyn-70', model: './apps/props/dynamic/industrial_pipe_cb740c0c_v4.glb', position: [30, 51, 256], app: 'prop-dynamic' },
160
+ { id: 'dyn-71', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v1.glb', position: [38, 51, 256], app: 'prop-dynamic' },
161
+ { id: 'dyn-72', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v2.glb', position: [46, 51, 256], app: 'prop-dynamic' },
162
+ { id: 'dyn-73', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v3.glb', position: [54, 51, 256], app: 'prop-dynamic' },
163
+ { id: 'dyn-74', model: './apps/props/dynamic/l-shaped_industrial_pipe_3b570c7e_v4.glb', position: [62, 51, 256], app: 'prop-dynamic' },
164
+ { id: 'dyn-75', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v1.glb', position: [70, 51, 256], app: 'prop-dynamic' },
165
+ { id: 'dyn-76', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v2.glb', position: [78, 51, 256], app: 'prop-dynamic' },
166
+ { id: 'dyn-77', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v3.glb', position: [86, 51, 256], app: 'prop-dynamic' },
167
+ { id: 'dyn-78', model: './apps/props/dynamic/l-shaped_industrial_pipe_f7fd8524_v4.glb', position: [94, 51, 256], app: 'prop-dynamic' },
168
+ { id: 'dyn-79', model: './apps/props/dynamic/night_club_speakers_9155e359_v1.glb', position: [102, 51, 256], app: 'prop-dynamic' },
169
+ { id: 'dyn-80', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v1.glb', position: [30, 54, 264], app: 'prop-dynamic' },
170
+ { id: 'dyn-81', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v2.glb', position: [38, 54, 264], app: 'prop-dynamic' },
171
+ { id: 'dyn-82', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v3.glb', position: [46, 54, 264], app: 'prop-dynamic' },
172
+ { id: 'dyn-83', model: './apps/props/dynamic/old_cheap_couch_with_a_bad_floral_pattern_53278f1b_v4.glb', position: [54, 54, 264], app: 'prop-dynamic' },
173
+ { id: 'dyn-84', model: './apps/props/dynamic/server_rack_03b09d1f_v1.glb', position: [62, 54, 264], app: 'prop-dynamic' },
174
+ { id: 'dyn-85', model: './apps/props/dynamic/server_rack_03b09d1f_v2.glb', position: [70, 54, 264], app: 'prop-dynamic' },
175
+ { id: 'dyn-86', model: './apps/props/dynamic/server_rack_03b09d1f_v3.glb', position: [78, 54, 264], app: 'prop-dynamic' },
176
+ { id: 'dyn-87', model: './apps/props/dynamic/server_rack_03b09d1f_v4.glb', position: [86, 54, 264], app: 'prop-dynamic' },
177
+ { id: 'dyn-88', model: './apps/props/dynamic/server_rack_c2999a18_v1.glb', position: [94, 54, 264], app: 'prop-dynamic' },
178
+ { id: 'dyn-89', model: './apps/props/dynamic/server_rack_c2999a18_v2.glb', position: [102, 54, 264], app: 'prop-dynamic' },
179
+ { id: 'dyn-90', model: './apps/props/dynamic/server_rack_c2999a18_v3.glb', position: [30, 57, 272], app: 'prop-dynamic' },
180
+ { id: 'dyn-91', model: './apps/props/dynamic/server_rack_c2999a18_v4.glb', position: [38, 57, 272], app: 'prop-dynamic' },
181
+ { id: 'dyn-92', model: './apps/props/dynamic/shop_counter_f668a712_v1.glb', position: [46, 57, 272], app: 'prop-dynamic' },
182
+ { id: 'dyn-93', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v1.glb', position: [54, 57, 272], app: 'prop-dynamic' },
183
+ { id: 'dyn-94', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v2.glb', position: [62, 57, 272], app: 'prop-dynamic' },
184
+ { id: 'dyn-95', model: './apps/props/dynamic/warehouse_crate_6e8a0927_v4.glb', position: [70, 57, 272], app: 'prop-dynamic' },
185
+ { id: 'dyn-96', model: './apps/props/dynamic/water_tank_c27c18f7_v1.glb', position: [78, 57, 272], app: 'prop-dynamic' },
186
+ { id: 'dyn-97', model: './apps/props/dynamic/water_tank_c27c18f7_v2.glb', position: [86, 57, 272], app: 'prop-dynamic' },
187
+ { id: 'dyn-98', model: './apps/props/dynamic/water_tank_c27c18f7_v3.glb', position: [94, 57, 272], app: 'prop-dynamic' },
188
+ { id: 'dyn-99', model: './apps/props/dynamic/water_tank_c27c18f7_v4.glb', position: [102, 57, 272], app: 'prop-dynamic' }
62
189
  ],
63
- playerModel: './apps/tps-game/cleetus.vrm',
64
- spawnPoint: [-30, 7.6, -30]
190
+ spawnPoints: [
191
+ [10, 5, 10],
192
+ [200, 5, -10],
193
+ [400, 5, -10],
194
+ [610, 5, 5],
195
+ [800, 5, -10]
196
+ ],
197
+ spawnPoint: [10, 5, 10],
198
+ playerModel: './apps/tps-game/cleetus.vrm'
65
199
  }
200
+
package/client/app.js CHANGED
@@ -21,6 +21,37 @@ import { createLoadingScreen } from './createLoadingScreen.js'
21
21
  import { MobileControls, detectDevice } from './MobileControls.js'
22
22
  import { XRControls, createXRButton } from './XRControls.js'
23
23
 
24
+ function patchGLB(arrayBuffer) {
25
+ try {
26
+ const v = new DataView(arrayBuffer)
27
+ if (v.getUint32(0, true) !== 0x46546C67) return arrayBuffer
28
+ const jsonLen = v.getUint32(12, true)
29
+ const jsonBytes = new Uint8Array(arrayBuffer, 20, jsonLen)
30
+ const json = JSON.parse(new TextDecoder().decode(jsonBytes))
31
+ if (!json.textures) return arrayBuffer
32
+ json.textures = json.textures.map(t => {
33
+ if (t.source === undefined && (!t.extensions || !Object.keys(t.extensions).some(k => t.extensions[k]?.source !== undefined))) {
34
+ return { ...t, source: 0 }
35
+ }
36
+ return t
37
+ })
38
+ const patched = new TextEncoder().encode(JSON.stringify(json))
39
+ const pad = (4 - (patched.length % 4)) % 4
40
+ const out = new ArrayBuffer(12 + 8 + patched.length + pad + (arrayBuffer.byteLength - 20 - jsonLen))
41
+ const ov = new DataView(out)
42
+ const ou = new Uint8Array(out)
43
+ ov.setUint32(0, 0x46546C67, true)
44
+ ov.setUint32(4, v.getUint32(4, true), true)
45
+ ov.setUint32(8, out.byteLength, true)
46
+ ov.setUint32(12, patched.length + pad, true)
47
+ ov.setUint32(16, 0x4E4F534A, true)
48
+ ou.set(patched, 20)
49
+ for (let i = 0; i < pad; i++) ou[20 + patched.length + i] = 0x20
50
+ ou.set(new Uint8Array(arrayBuffer, 20 + jsonLen), 20 + patched.length + pad)
51
+ return out
52
+ } catch (_) { return arrayBuffer }
53
+ }
54
+
24
55
  const loadingMgr = new LoadingManager()
25
56
  const loadingScreen = createLoadingScreen(loadingMgr)
26
57
  loadingMgr.setStage('CONNECTING')
@@ -705,6 +736,21 @@ let vrmBuffer = null
705
736
  let animAssets = null
706
737
  let assetsReady = null
707
738
  let assetsLoaded = false
739
+ const MAX_VRM_CONCURRENT = 6
740
+ let _vrmActive = 0
741
+ const _vrmQueue = []
742
+ function _vrmSlot() {
743
+ if (_vrmActive >= MAX_VRM_CONCURRENT || _vrmQueue.length === 0) return
744
+ _vrmActive++
745
+ const resolve = _vrmQueue.shift()
746
+ resolve()
747
+ }
748
+ function acquireVrmSlot() {
749
+ return new Promise(r => { _vrmQueue.push(r); _vrmSlot() })
750
+ }
751
+ function releaseVrmSlot() {
752
+ _vrmActive--; _vrmSlot()
753
+ }
708
754
 
709
755
  function detectVrmVersion(buffer) {
710
756
  try {
@@ -739,6 +785,8 @@ async function createPlayerVRM(id) {
739
785
  playerMeshes.set(id, group)
740
786
  if (assetsReady) await assetsReady
741
787
  if (!vrmBuffer) return group
788
+ await acquireVrmSlot()
789
+ if (!playerMeshes.has(id)) { releaseVrmSlot(); return group }
742
790
  try {
743
791
  const gltf = await gltfLoader.parseAsync(vrmBuffer.buffer.slice(0), '')
744
792
  const vrm = gltf.userData.vrm
@@ -786,7 +834,7 @@ async function createPlayerVRM(id) {
786
834
  console.log('[shader] vrm warmup compile done (fallback)')
787
835
  })
788
836
  }
789
- } catch (e) { console.error('[vrm]', id, e.message) }
837
+ } catch (e) { console.error('[vrm]', id, e.message) } finally { releaseVrmSlot() }
790
838
  return group
791
839
  }
792
840
 
@@ -979,8 +1027,11 @@ function loadEntityModel(entityId, entityState) {
979
1027
  const mp = entityState.position; model.position.set(mp[0], mp[1], mp[2])
980
1028
  const mr = entityState.rotation; if (mr) model.quaternion.set(mr[0], mr[1], mr[2], mr[3])
981
1029
  const colliders = []
1030
+ const SKIP_MATS = new Set(['aaatrigger', '{invisible', 'playerclip', 'clip', 'nodraw', 'trigger', 'sky', 'toolsclip', 'toolsplayerclip', 'toolsnodraw', 'toolsskybox', 'toolstrigger'])
982
1031
  model.traverse(c => {
983
1032
  if (c.isMesh) {
1033
+ const matName = (c.material?.name || '').toLowerCase()
1034
+ if (SKIP_MATS.has(matName) || SKIP_MATS.has(c.material?.name)) { c.visible = false; return }
984
1035
  c.castShadow = true
985
1036
  c.receiveShadow = true
986
1037
  if (!c.isSkinnedMesh) { c.matrixAutoUpdate = false; c.geometry.computeBoundsTree(); colliders.push(c) }
@@ -1000,7 +1051,7 @@ function loadEntityModel(entityId, entityState) {
1000
1051
  }
1001
1052
  const onGltfErr = (err) => { console.error('[gltf]', url, err); pendingLoads.delete(entityId); if (firstSnapshotEntityPending.has(entityId)) { firstSnapshotEntityPending.delete(entityId); if (firstSnapshotEntityPending.size === 0) checkAllLoaded() } }
1002
1053
  fetchCached(url).then(buf => {
1003
- gltfLoader.parse(buf.buffer.slice(0), '', onGltfLoad, onGltfErr)
1054
+ gltfLoader.parse(patchGLB(buf.buffer.slice(0)), '', onGltfLoad, onGltfErr)
1004
1055
  }).catch(onGltfErr)
1005
1056
  }
1006
1057
 
@@ -1047,8 +1098,24 @@ const client = new PhysicsNetworkClient({
1047
1098
  predictionEnabled: true,
1048
1099
  smoothInterpolation: true,
1049
1100
  onStateUpdate: (state) => {
1050
- for (const p of state.players) {
1051
- if (!playerMeshes.has(p.id)) createPlayerVRM(p.id)
1101
+ const myPos = state.players.find(p => p.id === client.playerId)?.position
1102
+ const sorted = myPos ? [...state.players].sort((a, b) => {
1103
+ if (a.id === client.playerId) return -1
1104
+ if (b.id === client.playerId) return 1
1105
+ const da = (a.position[0]-myPos[0])**2+(a.position[1]-myPos[1])**2+(a.position[2]-myPos[2])**2
1106
+ const db = (b.position[0]-myPos[0])**2+(b.position[1]-myPos[1])**2+(b.position[2]-myPos[2])**2
1107
+ return da - db
1108
+ }) : state.players
1109
+ const MAX_VISIBLE_PLAYERS = 32
1110
+ for (let i = 0; i < sorted.length; i++) {
1111
+ const p = sorted[i]
1112
+ if (!playerMeshes.has(p.id)) {
1113
+ if (i < MAX_VISIBLE_PLAYERS) createPlayerVRM(p.id)
1114
+ else { const g = new THREE.Group(); scene.add(g); playerMeshes.set(p.id, g) }
1115
+ }
1116
+ }
1117
+ for (const [id] of playerMeshes) {
1118
+ if (!state.players.find(p => p.id === id)) removePlayerMesh(id)
1052
1119
  }
1053
1120
  for (const e of state.entities) {
1054
1121
  const mesh = entityMeshes.get(e.id)
@@ -1079,7 +1146,7 @@ const client = new PhysicsNetworkClient({
1079
1146
  worldConfig = wd
1080
1147
  if (wd.playerModel) initAssets(wd.playerModel.startsWith('./') ? '/' + wd.playerModel.slice(2) : wd.playerModel)
1081
1148
  else { assetsReady = Promise.resolve(); assetsLoaded = true; checkAllLoaded() }
1082
- if (wd.entities) for (const e of wd.entities) { if (e.app) entityAppMap.set(e.id, e.app); if (e.model && !entityMeshes.has(e.id)) loadEntityModel(e.id, e) }
1149
+ if (wd.entities) for (const e of wd.entities) { if (e.app) entityAppMap.set(e.id, e.app) }
1083
1150
  if (wd.scene) applySceneConfig(wd.scene)
1084
1151
  if (wd.camera) cam.applyConfig(wd.camera)
1085
1152
  if (wd.input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.82",
3
+ "version": "0.1.84",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -126,7 +126,21 @@ export class AppRuntime {
126
126
  const ctx = this.contexts.get(entityId); if (!ctx) continue
127
127
  this._safeCall(appDef.server || appDef, 'update', [ctx, dt], `update(${entityId})`)
128
128
  }
129
- this._tickTimers(dt); this._spatialSync(); this._tickCollisions(); this._tickInteractables()
129
+ this._tickTimers(dt); this._syncDynamicBodies(); this._spatialSync(); this._tickCollisions(); this._tickInteractables()
130
+ }
131
+
132
+ _syncDynamicBodies() {
133
+ if (!this._physics) return
134
+ for (const e of this.entities.values()) {
135
+ if (e.bodyType !== 'dynamic' || e._physicsBodyId === undefined) continue
136
+ const active = this._physics.isBodyActive(e._physicsBodyId)
137
+ if (!active && e._dynSleeping) continue
138
+ e._dynSleeping = !active
139
+ const p = this._physics.getBodyPosition(e._physicsBodyId)
140
+ const r = this._physics.getBodyRotation(e._physicsBodyId)
141
+ e.position[0] = p[0]; e.position[1] = p[1]; e.position[2] = p[2]
142
+ e.rotation[0] = r[0]; e.rotation[1] = r[1]; e.rotation[2] = r[2]; e.rotation[3] = r[3]
143
+ }
130
144
  }
131
145
 
132
146
  _encodeEntity(id, e) {
@@ -143,7 +157,9 @@ export class AppRuntime {
143
157
  getSnapshotForPlayer(playerPosition, radius) {
144
158
  const relevant = new Set(this.relevantEntities(playerPosition, radius))
145
159
  const entities = []
146
- for (const [id, e] of this.entities) { if (relevant.has(id)) entities.push(this._encodeEntity(id, e)) }
160
+ for (const [id, e] of this.entities) {
161
+ if (relevant.has(id) || e._appName === 'environment') entities.push(this._encodeEntity(id, e))
162
+ }
147
163
  return { tick: this.currentTick, timestamp: Date.now(), entities }
148
164
  }
149
165
 
@@ -207,7 +223,19 @@ export class AppRuntime {
207
223
  }
208
224
  }
209
225
 
210
- _colR(c) { return !c ? 0 : c.type === 'sphere' ? (c.radius||1) : c.type === 'capsule' ? Math.max(c.radius||0.5,(c.height||1)/2) : c.type === 'box' ? Math.max(...(c.size||c.halfExtents||[1,1,1])) : 1 }
226
+ _colR(c) {
227
+ if (!c) return 0
228
+ if (c.type === 'sphere') return c.radius || 1
229
+ if (c.type === 'capsule') return Math.max(c.radius || 0.5, (c.height || 1) / 2)
230
+ if (c.type === 'box') {
231
+ const s = c.size; const h = c.halfExtents
232
+ if (Array.isArray(s)) return Math.max(...s)
233
+ if (typeof s === 'number') return s
234
+ if (Array.isArray(h)) return Math.max(...h)
235
+ return 1
236
+ }
237
+ return 1
238
+ }
211
239
  setPlayerManager(pm) { this._playerManager = pm }
212
240
  setStageLoader(sl) { this._stageLoader = sl }
213
241
  getPlayers() { return this._playerManager ? this._playerManager.getConnectedPlayers() : [] }
@@ -29,8 +29,12 @@ function encodeEntity(e) {
29
29
  }
30
30
 
31
31
  export class SnapshotEncoder {
32
- static encodeDelta(snapshot, prevEntityMap) {
33
- const players = (snapshot.players || []).map(encodePlayer)
32
+ static encodePlayers(players) {
33
+ return (players || []).map(encodePlayer)
34
+ }
35
+
36
+ static encodeDelta(snapshot, prevEntityMap, preEncodedPlayers) {
37
+ const players = preEncodedPlayers || (snapshot.players || []).map(encodePlayer)
34
38
  const currentIds = new Set()
35
39
  const entities = []
36
40
  const nextMap = new Map()
@@ -389,6 +389,9 @@ export async function extractAllMeshesFromGLBAsync(filepath) {
389
389
 
390
390
  // Build a node->transform map for node hierarchy
391
391
  const nodeTransforms = buildNodeTransforms(json)
392
+ const materials = json.materials || []
393
+ // Source Engine / CS:GO invisible/trigger materials — exclude from physics
394
+ const SKIP_MATS = new Set(['aaatrigger', '{invisible', 'playerclip', 'clip', 'nodraw', 'trigger', 'sky', 'toolsclip', 'toolsplayerclip', 'toolsnodraw', 'toolsskybox', 'toolstrigger'])
392
395
 
393
396
  for (let meshIdx = 0; meshIdx < (json.meshes || []).length; meshIdx++) {
394
397
  const mesh = json.meshes[meshIdx]
@@ -398,6 +401,9 @@ export async function extractAllMeshesFromGLBAsync(filepath) {
398
401
 
399
402
  for (let primIdx = 0; primIdx < mesh.primitives.length; primIdx++) {
400
403
  const prim = mesh.primitives[primIdx]
404
+ // Skip invisible/trigger materials that should not have physics collision
405
+ const matName = prim.material !== undefined ? (materials[prim.material]?.name || '') : ''
406
+ if (SKIP_MATS.has(matName)) continue
401
407
  let result
402
408
  try {
403
409
  if (prim.extensions?.KHR_draco_mesh_compression) {
@@ -267,6 +267,10 @@ export class PhysicsWorld {
267
267
  this.Jolt.destroy(v)
268
268
  return r
269
269
  }
270
+ isBodyActive(bodyId) {
271
+ const b = this._getBody(bodyId); if (!b) return false
272
+ const id = b.GetID(); const r = this.bodyInterface.IsActive(id); this.Jolt.destroy(id); return r
273
+ }
270
274
  setBodyPosition(bodyId, position) {
271
275
  const b = this._getBody(bodyId); if (!b) return
272
276
  const p = this._tmpRVec3 || new this.Jolt.RVec3(0, 0, 0); p.Set(position[0], position[1], position[2])
@@ -2,12 +2,12 @@ import { WebSocket } from 'ws'
2
2
  import { pack, unpack } from '../protocol/msgpack.js'
3
3
 
4
4
  const CONFIG = {
5
- botCount: 100,
6
- durationMs: 60000,
7
- inputHz: 60,
5
+ botCount: parseInt(process.env.BOT_COUNT || '100'),
6
+ durationMs: parseInt(process.env.BOT_DURATION || '60000'),
7
+ inputHz: parseInt(process.env.BOT_HZ || '60'),
8
8
  serverUrl: process.env.BOT_URL || 'ws://localhost:3001/ws',
9
- batchSize: 10,
10
- batchDelayMs: 200
9
+ batchSize: parseInt(process.env.BOT_BATCH || '20'),
10
+ batchDelayMs: parseInt(process.env.BOT_DELAY || '100')
11
11
  }
12
12
 
13
13
  const MSG_INPUT = 0x11
@@ -33,7 +33,9 @@ export function createServerAPI(ctx) {
33
33
 
34
34
  async loadWorld(worldDef) {
35
35
  ctx.currentWorldDef = worldDef
36
- if (worldDef.spawnPoint) ctx.worldSpawnPoint = [...worldDef.spawnPoint]
36
+ if (worldDef.spawnPoints?.length) ctx.worldSpawnPoints = worldDef.spawnPoints
37
+ else if (worldDef.spawnPoint) ctx.worldSpawnPoints = [worldDef.spawnPoint]
38
+ ctx.worldSpawnPoint = ctx.worldSpawnPoints?.[0] || worldDef.spawnPoint || [0, 5, 0]
37
39
  await appLoader.loadAll()
38
40
  const stage = stageLoader.loadFromDefinition('main', worldDef)
39
41
  return { entities: new Map(), apps: new Map(), count: stage.entityCount }
@@ -5,7 +5,8 @@ export function createConnectionHandlers(ctx) {
5
5
  const { tickSystem, playerManager, networkState, lagCompensator, physicsIntegration, connections, sessions, appLoader, appRuntime, emitter, inspector } = ctx
6
6
 
7
7
  function onClientConnect(transport) {
8
- const sp = [...ctx.worldSpawnPoint]
8
+ const spawnPoints = ctx.worldSpawnPoints || [ctx.worldSpawnPoint]
9
+ const sp = [...spawnPoints[Math.floor(Math.random() * spawnPoints.length)]]
9
10
  const playerConfig = ctx.currentWorldDef?.player || {}
10
11
  const playerId = playerManager.addPlayer(transport, { position: sp, health: playerConfig.health })
11
12
  networkState.addPlayer(playerId, { position: sp })
@@ -23,8 +24,11 @@ export function createConnectionHandlers(ctx) {
23
24
  for (const [appName, code] of Object.entries(clientModules)) {
24
25
  connections.send(playerId, MSG.APP_MODULE, { app: appName, code })
25
26
  }
26
- const snap = appRuntime.getSnapshot()
27
- connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(snap) })
27
+ const relevanceRadius = ctx.currentWorldDef?.relevanceRadius || 0
28
+ const snapEntities = relevanceRadius > 0 ? appRuntime.getSnapshotForPlayer(sp, relevanceRadius) : appRuntime.getSnapshot()
29
+ const playerSnap = networkState.getSnapshot()
30
+ const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: snapEntities.entities }
31
+ connections.send(playerId, MSG.SNAPSHOT, { seq: ++ctx.snapshotSeq, ...SnapshotEncoder.encode(combined) })
28
32
  for (const [entityId] of appRuntime.apps) appRuntime.fireMessage(entityId, { type: 'player_join', playerId })
29
33
  emitter.emit('playerJoin', { id: playerId })
30
34
  }
@@ -5,13 +5,13 @@ import { isUnreliable } from '../protocol/MessageTypes.js'
5
5
  import { applyMovement as _applyMovement, DEFAULT_MOVEMENT as _DEFAULT_MOVEMENT } from '../shared/movement.js'
6
6
 
7
7
  const KEYFRAME_INTERVAL = 128
8
- const SNAP_GROUPS = 4
8
+ const MAX_SENDS_PER_TICK = 25
9
9
 
10
10
  export function createTickHandler(deps) {
11
11
  const {
12
12
  networkState, playerManager, physicsIntegration,
13
13
  lagCompensator, physics, appRuntime, connections,
14
- movement: m = {}, stageLoader, eventLog, _movement
14
+ movement: m = {}, stageLoader, eventLog, _movement, getRelevanceRadius
15
15
  } = deps
16
16
  const applyMovement = _movement?.applyMovement || _applyMovement
17
17
  const DEFAULT_MOVEMENT = _movement?.DEFAULT_MOVEMENT || _DEFAULT_MOVEMENT
@@ -119,16 +119,20 @@ export function createTickHandler(deps) {
119
119
  const playerSnap = networkState.getSnapshot()
120
120
  snapshotSeq++
121
121
  const isKeyframe = snapshotSeq % KEYFRAME_INTERVAL === 0
122
- const curGroup = tick % SNAP_GROUPS
122
+ const snapGroups = Math.max(1, Math.ceil(players.length / MAX_SENDS_PER_TICK))
123
+ const curGroup = tick % snapGroups
123
124
 
124
- if (stageLoader && stageLoader.getActiveStage()) {
125
- const relevanceRadius = stageLoader.getActiveStage().spatial.relevanceRadius
125
+ const relevanceRadius = (stageLoader && stageLoader.getActiveStage())
126
+ ? stageLoader.getActiveStage().spatial.relevanceRadius
127
+ : (getRelevanceRadius ? getRelevanceRadius() : 0)
128
+ if (relevanceRadius > 0) {
129
+ const preEncodedPlayers = SnapshotEncoder.encodePlayers(playerSnap.players)
126
130
  for (const player of players) {
127
- if (!isKeyframe && player.id % SNAP_GROUPS !== curGroup) continue
131
+ if (!isKeyframe && player.id % snapGroups !== curGroup) continue
128
132
  const entitySnap = appRuntime.getSnapshotForPlayer(player.state.position, relevanceRadius)
129
- const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, players: playerSnap.players, entities: entitySnap.entities }
133
+ const combined = { tick: playerSnap.tick, timestamp: playerSnap.timestamp, entities: entitySnap.entities }
130
134
  const prevMap = (isKeyframe || !playerEntityMaps.has(player.id)) ? new Map() : playerEntityMaps.get(player.id)
131
- const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap)
135
+ const { encoded, entityMap } = SnapshotEncoder.encodeDelta(combined, prevMap, preEncodedPlayers)
132
136
  playerEntityMaps.set(player.id, entityMap)
133
137
  connections.send(player.id, MSG.SNAPSHOT, { seq: snapshotSeq, ...encoded })
134
138
  }
@@ -140,7 +144,7 @@ export function createTickHandler(deps) {
140
144
  broadcastEntityMap = entityMap
141
145
  const data = pack({ type: MSG.SNAPSHOT, payload: { seq: snapshotSeq, ...encoded } })
142
146
  for (const player of players) {
143
- if (!isKeyframe && player.id % SNAP_GROUPS !== curGroup) continue
147
+ if (!isKeyframe && player.id % snapGroups !== curGroup) continue
144
148
  connections.sendPacked(player.id, data, snapUnreliable)
145
149
  }
146
150
  }
package/src/sdk/server.js CHANGED
@@ -171,7 +171,8 @@ export async function createServer(config = {}) {
171
171
  connections,
172
172
  movement,
173
173
  stageLoader,
174
- eventLog
174
+ eventLog,
175
+ getRelevanceRadius: () => ctx.currentWorldDef?.relevanceRadius || 0
175
176
  }))
176
177
 
177
178
  const { onClientConnect } = createConnectionHandlers(ctx)
@@ -46,10 +46,7 @@ export class Stage {
46
46
  }
47
47
 
48
48
  getRelevantEntities(position, radius) {
49
- const nearby = this.spatial.nearby(position, radius || this.spatial.relevanceRadius)
50
- const set = new Set(nearby)
51
- for (const sid of this._staticIds) set.add(sid)
52
- return Array.from(set)
49
+ return this.spatial.nearby(position, radius || this.spatial.relevanceRadius)
53
50
  }
54
51
 
55
52
  getStaticIds() {