spoint 0.1.505 → 0.1.506

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.
@@ -1,267 +1,108 @@
1
1
  export default {
2
2
  server: {
3
3
  setup(ctx) {
4
- const roomSize = 30
5
- const wallHeight = 8
6
- const wallThickness = 0.5
4
+ console.log('[matrix-construct-room] server setup')
5
+ const planeSize = 50
6
+ const planeThickness = 0.5
7
7
 
8
- // Floor
8
+ // Floor plane with physics
9
9
  ctx.physics.addColliderFromConfig({
10
10
  type: 'box',
11
- size: [roomSize / 2, wallThickness / 2, roomSize / 2],
12
- position: [0, -wallThickness / 2, 0],
11
+ size: [planeSize / 2, planeThickness / 2, planeSize / 2],
12
+ position: [0, -planeThickness / 2, 0],
13
13
  mass: 0,
14
14
  dynamic: false
15
15
  })
16
16
 
17
- // Ceiling
18
- ctx.physics.addColliderFromConfig({
19
- type: 'box',
20
- size: [roomSize / 2, wallThickness / 2, roomSize / 2],
21
- position: [0, wallHeight + wallThickness / 2, 0],
22
- mass: 0,
23
- dynamic: false
24
- })
25
-
26
- // Walls - back (negative Z)
27
- ctx.physics.addColliderFromConfig({
28
- type: 'box',
29
- size: [roomSize / 2, wallHeight / 2, wallThickness / 2],
30
- position: [0, wallHeight / 2, -roomSize / 2],
31
- mass: 0,
32
- dynamic: false
33
- })
34
-
35
- // Walls - front (positive Z)
36
- ctx.physics.addColliderFromConfig({
37
- type: 'box',
38
- size: [roomSize / 2, wallHeight / 2, wallThickness / 2],
39
- position: [0, wallHeight / 2, roomSize / 2],
40
- mass: 0,
41
- dynamic: false
42
- })
43
-
44
- // Walls - left (negative X)
45
- ctx.physics.addColliderFromConfig({
46
- type: 'box',
47
- size: [wallThickness / 2, wallHeight / 2, roomSize / 2],
48
- position: [-roomSize / 2, wallHeight / 2, 0],
49
- mass: 0,
50
- dynamic: false
51
- })
52
-
53
- // Walls - right (positive X)
54
- ctx.physics.addColliderFromConfig({
55
- type: 'box',
56
- size: [wallThickness / 2, wallHeight / 2, roomSize / 2],
57
- position: [roomSize / 2, wallHeight / 2, 0],
58
- mass: 0,
59
- dynamic: false
60
- })
61
-
62
- // Store room dimensions for client rendering
63
- ctx.state.roomSize = roomSize
64
- ctx.state.wallHeight = wallHeight
65
- ctx.state.wallThickness = wallThickness
66
- ctx.state.gridSize = 2
17
+ ctx.state.planeSize = planeSize
18
+ ctx.state.planeThickness = planeThickness
67
19
  }
68
20
  },
69
21
  client: {
70
22
  setup(engine) {
71
- // Create the white room with grid lines
72
- this.createConstructRoom(engine)
23
+ console.log('[matrix-construct-room] client setup')
24
+ this.createConstructPlane(engine)
73
25
  },
74
26
  render(ctx) {
75
27
  return {
76
28
  position: [0, 0, 0],
77
29
  rotation: [0, 0, 0, 1],
78
- custom: {
79
- // Empty - we create the room in setup
80
- }
30
+ custom: {}
81
31
  }
82
32
  },
83
- createConstructRoom(engine) {
33
+ createConstructPlane(engine) {
84
34
  const { THREE, scene } = engine
85
- const roomSize = 30
86
- const wallHeight = 8
87
- const wallThickness = 0.5
35
+ const planeSize = 50
88
36
  const gridSize = 2
89
37
 
90
- // White material with slight emissive glow
91
- const whiteMaterial = new THREE.MeshStandardMaterial({
92
- color: 0xffffff,
93
- roughness: 0.1,
94
- metalness: 0,
95
- emissive: 0x222222,
96
- emissiveIntensity: 0.3,
97
- side: THREE.DoubleSide
98
- })
38
+ console.log('[matrix-construct-room] creating plane geometry')
99
39
 
100
- // Grid line material - subtle gray
101
- const gridMaterial = new THREE.MeshStandardMaterial({
102
- color: 0x888888,
103
- roughness: 0.3,
104
- metalness: 0,
105
- emissive: 0x111111,
106
- emissiveIntensity: 0.1,
107
- side: THREE.DoubleSide,
108
- transparent: true,
109
- opacity: 0.4
110
- })
111
-
112
- // Bright grid line material for main axes
113
- const axisMaterial = new THREE.MeshStandardMaterial({
40
+ // Simple gray plane
41
+ const planeMaterial = new THREE.MeshStandardMaterial({
114
42
  color: 0xcccccc,
115
- roughness: 0.2,
43
+ roughness: 0.8,
116
44
  metalness: 0,
117
- emissive: 0x333333,
118
- emissiveIntensity: 0.2,
119
45
  side: THREE.DoubleSide
120
46
  })
121
47
 
122
- // Floor
123
- const floorGeo = new THREE.BoxGeometry(roomSize, wallThickness, roomSize)
124
- const floor = new THREE.Mesh(floorGeo, whiteMaterial)
125
- floor.position.set(0, -wallThickness / 2, 0)
126
- floor.receiveShadow = true
127
- scene.add(floor)
128
-
129
- // Ceiling
130
- const ceilGeo = new THREE.BoxGeometry(roomSize, wallThickness, roomSize)
131
- const ceil = new THREE.Mesh(ceilGeo, whiteMaterial)
132
- ceil.position.set(0, wallHeight + wallThickness / 2, 0)
133
- scene.add(ceil)
134
-
135
- // Walls
136
- const wallGeo = new THREE.BoxGeometry(roomSize, wallHeight, wallThickness)
137
- const sideWallGeo = new THREE.BoxGeometry(wallThickness, wallHeight, roomSize)
138
-
139
- // Back wall (negative Z)
140
- const backWall = new THREE.Mesh(wallGeo, whiteMaterial)
141
- backWall.position.set(0, wallHeight / 2, -roomSize / 2)
142
- backWall.receiveShadow = true
143
- scene.add(backWall)
144
-
145
- // Front wall (positive Z)
146
- const frontWall = new THREE.Mesh(wallGeo, whiteMaterial)
147
- frontWall.position.set(0, wallHeight / 2, roomSize / 2)
148
- frontWall.receiveShadow = true
149
- scene.add(frontWall)
150
-
151
- // Left wall (negative X)
152
- const leftWall = new THREE.Mesh(sideWallGeo, whiteMaterial)
153
- leftWall.position.set(-roomSize / 2, wallHeight / 2, 0)
154
- leftWall.receiveShadow = true
155
- scene.add(leftWall)
156
-
157
- // Right wall (positive X)
158
- const rightWall = new THREE.Mesh(sideWallGeo, whiteMaterial)
159
- rightWall.position.set(roomSize / 2, wallHeight / 2, 0)
160
- rightWall.receiveShadow = true
161
- scene.add(rightWall)
48
+ // Main plane
49
+ const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
50
+ const plane = new THREE.Mesh(planeGeo, planeMaterial)
51
+ plane.rotation.x = -Math.PI / 2
52
+ plane.position.y = -0.25
53
+ plane.receiveShadow = true
54
+ scene.add(plane)
162
55
 
163
- // Grid lines on floor
56
+ // Grid lines - blue X axis, red Z axis (matching original matrix room)
164
57
  const lineThickness = 0.05
165
- const lineGeo = new THREE.BoxGeometry(roomSize, lineThickness, lineThickness)
166
- const crossLineGeo = new THREE.BoxGeometry(lineThickness, lineThickness, roomSize)
167
-
168
- // Floor grid
169
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
170
- // X-axis lines
171
- const lineX = new THREE.Mesh(lineGeo, i === 0 ? axisMaterial : gridMaterial)
172
- lineX.position.set(0, lineThickness / 2, i)
173
- scene.add(lineX)
174
-
175
- // Z-axis lines
176
- const lineZ = new THREE.Mesh(crossLineGeo, i === 0 ? axisMaterial : gridMaterial)
177
- lineZ.position.set(i, lineThickness / 2, 0)
178
- scene.add(lineZ)
58
+ const halfSize = planeSize / 2
59
+
60
+ const gridMat = new THREE.MeshBasicMaterial({ color: 0x888888 })
61
+ const xMat = new THREE.MeshBasicMaterial({ color: 0x0000ff }) // blue X axis
62
+ const zMat = new THREE.MeshBasicMaterial({ color: 0xff0000 }) // red Z axis
63
+
64
+ for (let x = -halfSize; x <= halfSize; x += gridSize) {
65
+ const mat = Math.abs(x) < 0.01 ? xMat : gridMat
66
+ const geo = new THREE.BoxGeometry(lineThickness, lineThickness, planeSize)
67
+ const line = new THREE.Mesh(geo, mat)
68
+ line.position.set(x, lineThickness / 2, 0)
69
+ scene.add(line)
179
70
  }
180
-
181
- // Wall grids - back wall
182
- const wallLineGeoH = new THREE.BoxGeometry(roomSize, lineThickness, lineThickness)
183
- const wallLineGeoV = new THREE.BoxGeometry(lineThickness, wallHeight, lineThickness)
184
-
185
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
186
- // Horizontal lines
187
- const hLine = new THREE.Mesh(wallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
188
- hLine.position.set(0, -wallHeight / 2 + i + wallHeight / 2, -roomSize / 2 + wallThickness / 2 + lineThickness)
189
- scene.add(hLine)
190
-
191
- // Vertical lines
192
- const vLine = new THREE.Mesh(wallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
193
- vLine.position.set(-roomSize / 2 + i, 0, -roomSize / 2 + wallThickness / 2 + lineThickness)
194
- scene.add(vLine)
71
+ for (let z = -halfSize; z <= halfSize; z += gridSize) {
72
+ const mat = Math.abs(z) < 0.01 ? zMat : gridMat
73
+ const geo = new THREE.BoxGeometry(planeSize, lineThickness, lineThickness)
74
+ const line = new THREE.Mesh(geo, mat)
75
+ line.position.set(0, lineThickness / 2, z)
76
+ scene.add(line)
195
77
  }
196
78
 
197
- // Front wall grid
198
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
199
- const hLine = new THREE.Mesh(wallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
200
- hLine.position.set(0, -wallHeight / 2 + i + wallHeight / 2, roomSize / 2 - wallThickness / 2 - lineThickness)
201
- scene.add(hLine)
202
-
203
- const vLine = new THREE.Mesh(wallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
204
- vLine.position.set(-roomSize / 2 + i, 0, roomSize / 2 - wallThickness / 2 - lineThickness)
205
- scene.add(vLine)
206
- }
207
-
208
- // Left wall grid
209
- const sideWallLineGeoH = new THREE.BoxGeometry(lineThickness, lineThickness, roomSize)
210
- const sideWallLineGeoV = new THREE.BoxGeometry(lineThickness, wallHeight, lineThickness)
211
-
212
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
213
- const hLine = new THREE.Mesh(sideWallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
214
- hLine.position.set(-roomSize / 2 + wallThickness / 2 + lineThickness, -wallHeight / 2 + i + wallHeight / 2, 0)
215
- scene.add(hLine)
216
-
217
- const vLine = new THREE.Mesh(sideWallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
218
- vLine.position.set(-roomSize / 2 + wallThickness / 2 + lineThickness, 0, -roomSize / 2 + i)
219
- scene.add(vLine)
220
- }
221
-
222
- // Right wall grid
223
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
224
- const hLine = new THREE.Mesh(sideWallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
225
- hLine.position.set(roomSize / 2 - wallThickness / 2 - lineThickness, -wallHeight / 2 + i + wallHeight / 2, 0)
226
- scene.add(hLine)
227
-
228
- const vLine = new THREE.Mesh(sideWallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
229
- vLine.position.set(roomSize / 2 - wallThickness / 2 - lineThickness, 0, -roomSize / 2 + i)
230
- scene.add(vLine)
231
- }
232
-
233
- // Ceiling grid
234
- for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
235
- const lineX = new THREE.Mesh(lineGeo, i === 0 ? axisMaterial : gridMaterial)
236
- lineX.position.set(0, wallHeight + wallThickness - lineThickness / 2, i)
237
- scene.add(lineX)
238
-
239
- const lineZ = new THREE.Mesh(crossLineGeo, i === 0 ? axisMaterial : gridMaterial)
240
- lineZ.position.set(i, wallHeight + wallThickness - lineThickness / 2, 0)
241
- scene.add(lineZ)
242
- }
243
-
244
- // Add subtle point lights for that construct glow
245
- const centerLight = new THREE.PointLight(0xffffff, 2, 50)
246
- centerLight.position.set(0, wallHeight / 2, 0)
247
- scene.add(centerLight)
248
-
249
- // Corner accent lights
250
- const cornerPositions = [
251
- [-roomSize / 2 + 2, wallHeight - 2, -roomSize / 2 + 2],
252
- [roomSize / 2 - 2, wallHeight - 2, -roomSize / 2 + 2],
253
- [-roomSize / 2 + 2, wallHeight - 2, roomSize / 2 - 2],
254
- [roomSize / 2 - 2, wallHeight - 2, roomSize / 2 - 2]
255
- ]
256
-
257
- cornerPositions.forEach(pos => {
258
- const light = new THREE.PointLight(0xffffff, 0.5, 15)
259
- light.position.set(...pos)
260
- scene.add(light)
79
+ // Origin marker
80
+ const originGeo = new THREE.RingGeometry(0.3, 0.5, 32)
81
+ const originMat = new THREE.MeshBasicMaterial({
82
+ color: 0x333333,
83
+ side: THREE.DoubleSide,
84
+ transparent: true,
85
+ opacity: 0.4
86
+ })
87
+ const origin = new THREE.Mesh(originGeo, originMat)
88
+ origin.rotation.x = -Math.PI / 2
89
+ origin.position.y = lineThickness
90
+ scene.add(origin)
91
+
92
+ // Outer boundary ring
93
+ const boundaryGeo = new THREE.RingGeometry(halfSize - 0.2, halfSize, 64)
94
+ const boundaryMat = new THREE.MeshBasicMaterial({
95
+ color: 0x666666,
96
+ side: THREE.DoubleSide,
97
+ transparent: true,
98
+ opacity: 0.3
261
99
  })
100
+ const boundary = new THREE.Mesh(boundaryGeo, boundaryMat)
101
+ boundary.rotation.x = -Math.PI / 2
102
+ boundary.position.y = lineThickness / 2
103
+ scene.add(boundary)
262
104
 
263
- // Store reference for cleanup if needed
264
- this.constructRoom = { floor, ceil, backWall, frontWall, leftWall, rightWall }
105
+ console.log('[matrix-construct-room] done creating plane and grid')
265
106
  }
266
107
  }
267
108
  }
@@ -1,4 +1,4 @@
1
- const MATRIX_CONSTRUCT = {
1
+ const CONSTRUCT = {
2
2
  enabled: false,
3
3
  anchorDir: [0, 1, 0],
4
4
  radius: 1000,
@@ -44,18 +44,20 @@ export default {
44
44
  feetOffset: 0.212
45
45
  },
46
46
  scene: {
47
- skyColor: 0xffffff,
48
- fogColor: 0xffffff,
49
- fogNear: 500,
50
- fogFar: 1000,
47
+ skyColor: 0x87ceeb,
48
+ fogColor: 0x87ceeb,
49
+ fogType: 'exp2',
50
+ fogDensity: 0.001,
51
+ fogNear: 200,
52
+ fogFar: 400,
51
53
  ambientColor: 0xffffff,
52
- ambientIntensity: 0.8,
54
+ ambientIntensity: 0.6,
53
55
  sunColor: 0xffffff,
54
- sunIntensity: 0.5,
55
- sunPosition: [0, 100, 0],
56
- fillColor: 0xffffff,
57
- fillIntensity: 0.5,
58
- fillPosition: [0, 50, 0],
56
+ sunIntensity: 1.2,
57
+ sunPosition: [30, 60, 30],
58
+ fillColor: 0x4488ff,
59
+ fillIntensity: 0.4,
60
+ fillPosition: [-30, 40, -30],
59
61
  shadowMapSize: 1024,
60
62
  shadowBias: 0.001,
61
63
  shadowNormalBias: 0.1
@@ -81,9 +83,9 @@ export default {
81
83
  },
82
84
  trustedApps: ['matrix-construct-room', 'tps-game'],
83
85
  placeableApps: [],
84
- terrain: MATRIX_CONSTRUCT,
86
+ terrain: CONSTRUCT,
85
87
  entities: [
86
- { id: 'terrain', app: 'terrain', config: MATRIX_CONSTRUCT },
88
+ { id: 'terrain', app: 'terrain', config: CONSTRUCT },
87
89
  { id: 'matrix-room', app: 'matrix-construct-room', position: [0, 0, 0] },
88
90
  { id: 'tps-game', position: [0, 0, 0], app: 'tps-game' }
89
91
  ],
@@ -4,8 +4,9 @@ export class MobileControls {
4
4
  constructor(options = {}) {
5
5
  this.enabled = isMobile || options.forceEnable
6
6
  this.responsive = this._calcResponsive()
7
- this.layout = this._calcLayout()
7
+ // Apply options first, then calculate layout with final values
8
8
  this.options = { joystickRadius: this.responsive.joystickRadius, lookJoystickRadius: this.responsive.joystickRadius, buttonSize: this.responsive.buttonSize, buttonSpacing: this.responsive.spacing, deadzone: 0.12, movementDeadzone: 0.15, rotationSensitivity: 0.003, zoomSensitivity: 0.008, autoShow: true, ...options }
9
+ this.layout = this._calcLayout()
9
10
  this.state = { move: { x: 0, y: 0 }, look: { x: 0, y: 0 }, lookDelta: { yaw: 0, pitch: 0 }, jump: false, shoot: false, reload: false, sprint: false, crouch: false, zoom: 0, zoomDelta: 0, interact: false, menu: false, chatWheel: false }
10
11
  // Opt-in gyro fine-aim: device tilt adds a small yaw/pitch delta on TOP of the touch-look joystick
11
12
  // delta, default OFF (a player who never enables it gets byte-identical touch-only behavior).
@@ -43,7 +44,7 @@ export class MobileControls {
43
44
  _calcLayout() {
44
45
  const { viewport: { w, h }, edgeMargin: m, bottomMargin: bm, joystickRadius: r, buttonSize: bs, spacing: sp } = this.responsive
45
46
  const d = r * 2, lr = Math.min(80, w * 0.15)
46
- return { moveJoystickPos: { x: m, y: -bm - d / 2 }, lookJoystickPos: { x: w - lr - d, y: -(bm + d / 2) }, moveLeft: m + 80, moveBottom: bm + d / 2, lookRight: lr, lookBottom: bm + d / 2, buttonsBottomOffset: bm + 60, buttonsRightOffset: Math.max(10, m), buttonAreaWidth: bs * 3 + sp * 4, w, h }
47
+ return { moveJoystickPos: { x: m, y: -bm - d / 2 }, lookJoystickPos: { x: w - lr - d, y: -(bm + d / 2) }, moveLeft: m, moveBottom: bm + d / 2, lookRight: lr, lookBottom: bm + d / 2, buttonsBottomOffset: bm + 60, buttonsRightOffset: Math.max(10, m), buttonAreaWidth: bs * 3 + sp * 4, w, h }
47
48
  }
48
49
 
49
50
  calculateResponsiveSizes() { return this._calcResponsive() }
@@ -194,11 +195,13 @@ export class MobileControls {
194
195
 
195
196
  getInput() {
196
197
  if (!this.enabled) return null
197
- const { move } = this.state, dz = 0.3
198
+ const { move } = this.state, dz = 0.2
198
199
  // Gyro delta adds ON TOP of the touch-look joystick delta (fine-aim nudge while thumb-dragging),
199
200
  // not a replacement -- so touch-look stays the primary input and gyro just sharpens it.
200
201
  const yaw = this.state.lookDelta.yaw + this._gyroDelta.yaw, pitch = this.state.lookDelta.pitch + this._gyroDelta.pitch
201
- return { forward: move.y < -dz, backward: move.y > dz, left: move.x < -dz, right: move.x > dz, jump: this.state.jump, shoot: this.state.shoot, reload: this.state.reload, sprint: this.state.sprint, crouch: this.state.crouch, yaw, pitch, zoom: this.state.zoomDelta, resetZoom: () => { this.state.zoomDelta = 0 }, moveX: move.x, moveY: move.y, mouseX: 0, mouseY: 0, interact: this.state.interact, analogForward: move.y, analogRight: move.x, chatWheel: this.state.chatWheel }
202
+ // Screen Y is DOWN; joystick UP (forward) = negative dy. Flip so forward = positive.
203
+ const forwardVal = -move.y
204
+ return { forward: forwardVal < -dz, backward: forwardVal > dz, left: move.x < -dz, right: move.x > dz, jump: this.state.jump, shoot: this.state.shoot, reload: this.state.reload, sprint: this.state.sprint, crouch: this.state.crouch, yaw, pitch, zoom: this.state.zoomDelta, resetZoom: () => { this.state.zoomDelta = 0 }, moveX: move.x, moveY: move.y, mouseX: 0, mouseY: 0, interact: this.state.interact, analogForward: forwardVal, analogRight: move.x, chatWheel: this.state.chatWheel }
202
205
  }
203
206
 
204
207
  hasInteraction() { return this.moveJoystick.active || this.lookJoystick.active || this.pinch.active || this.state.jump || this.state.shoot || this.state.reload || this.state.sprint || this.state.crouch || this.state.interact || this.state.chatWheel || this.state.zoomDelta !== 0 || (this.gyroAimEnabled && (this._gyroDelta.yaw !== 0 || this._gyroDelta.pitch !== 0)) }
@@ -13,7 +13,7 @@ export { installUnderwaterTint, setSeaLevelY }
13
13
  export function createScene() {
14
14
  const scene = new THREE.Scene()
15
15
  scene.background = new THREE.Color(0x87ceeb)
16
- scene.fog = new THREE.Fog(0x87ceeb, 80, 200)
16
+ scene.fog = new THREE.FogExp2(0x87ceeb, 0.0025)
17
17
  if (typeof window !== 'undefined' && !Number.isFinite(window.__fogFar)) window.__fogFar = 200
18
18
  installUnderwaterTint()
19
19
  installSkeletonUploadSkip()
@@ -230,7 +230,13 @@ export function createRenderer(isMobile) {
230
230
  if (_ctxLostSceneState) {
231
231
  try {
232
232
  if (_ctxLostSceneState.background != null) scene.background = new THREE.Color(_ctxLostSceneState.background)
233
- if (_ctxLostSceneState.fog) scene.fog = new THREE.Fog(_ctxLostSceneState.fog.color, _ctxLostSceneState.fog.near, _ctxLostSceneState.fog.far)
233
+ if (_ctxLostSceneState.fog) {
234
+ if (_ctxLostSceneState.fog.type === 'exp2') {
235
+ scene.fog = new THREE.FogExp2(_ctxLostSceneState.fog.color, _ctxLostSceneState.fog.density)
236
+ } else {
237
+ scene.fog = new THREE.Fog(_ctxLostSceneState.fog.color, _ctxLostSceneState.fog.near, _ctxLostSceneState.fog.far)
238
+ }
239
+ }
234
240
  } catch (_) {}
235
241
  }
236
242
  location.reload()
@@ -340,7 +346,15 @@ export function createLoaders(renderer) {
340
346
 
341
347
  export function applySceneConfig(s, scene, ambient, sun, studio, camera) {
342
348
  if (s.skyColor != null) scene.background = new THREE.Color(s.skyColor)
343
- if (s.fogColor != null) { const _far = s.fogFar ?? 200; scene.fog = new THREE.Fog(s.fogColor, s.fogNear ?? 80, _far); if (typeof window !== 'undefined') window.__fogFar = _far }
349
+ if (s.fogColor != null) {
350
+ const _far = s.fogFar ?? 200
351
+ if (s.fogType === 'exp2') {
352
+ scene.fog = new THREE.FogExp2(s.fogColor, s.fogDensity ?? 0.0025)
353
+ } else {
354
+ scene.fog = new THREE.Fog(s.fogColor, s.fogNear ?? 80, _far)
355
+ }
356
+ if (typeof window !== 'undefined') window.__fogFar = _far
357
+ }
344
358
  if (s.ambientColor != null) { ambient.color.set(s.ambientColor); ambient.intensity = s.ambientIntensity ?? 0.3 }
345
359
  if (s.sunColor != null) { sun.color.set(s.sunColor); sun.intensity = s.sunIntensity ?? 1.5 }
346
360
  if (s.sunPosition) sun.position.set(...s.sunPosition)
@@ -6,10 +6,10 @@ const CSS = `
6
6
  @keyframes joyGlowLook{0%{box-shadow:0 0 15px color-mix(in oklab, var(--accent) 35%, transparent),inset 0 0 20px color-mix(in oklab, var(--accent) 8%, transparent)}100%{box-shadow:0 0 25px color-mix(in oklab, var(--accent) 55%, transparent),inset 0 0 30px color-mix(in oklab, var(--accent) 18%, transparent)}}
7
7
  @keyframes fadeIn{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
8
8
  .mobile-joystick-container{position:absolute;pointer-events:auto;touch-action:none;opacity:0;animation:fadeIn .4s ease-out forwards;animation-delay:.1s;padding:0;background:transparent;border:0}
9
- .mobile-joystick-base{position:absolute;width:100%;height:100%;border-radius:50%;background:radial-gradient(circle at 30% 30%, color-mix(in oklab, var(--panel-1) 70%, transparent), color-mix(in oklab, var(--panel-1) 90%, transparent));border:2px solid color-mix(in oklab, var(--rule) 80%, transparent);box-shadow:0 4px 20px color-mix(in oklab, var(--panel-text) 12%, transparent),inset 0 2px 10px color-mix(in oklab, var(--panel-text) 8%, transparent);transition:border-color .15s,box-shadow .15s}
9
+ .mobile-joystick-base{position:absolute;width:100%;height:100%;border-radius:50%;background:transparent;border:1px solid color-mix(in oklab, var(--rule) 50%, transparent);box-shadow:0 2px 10px color-mix(in oklab, var(--panel-text) 8%, transparent),inset 0 1px 5px color-mix(in oklab, var(--panel-text) 4%, transparent);transition:border-color .15s,box-shadow .15s}
10
10
  .mobile-joystick-base.active{border-color:color-mix(in oklab, var(--accent) 70%, transparent);animation:joyGlow 1.5s ease-in-out infinite}
11
11
  .mobile-joystick-base.look-active{border-color:color-mix(in oklab, var(--accent) 60%, transparent);animation:joyGlowLook 1.5s ease-in-out infinite}
12
- .mobile-joystick-knob{position:absolute;top:50%;left:50%;width:56px;height:56px;border-radius:50%;background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--panel-text) 40%, transparent), color-mix(in oklab, var(--panel-text-3) 50%, transparent));border:2px solid color-mix(in oklab, var(--panel-text) 40%, transparent);transform:translate(-50%,-50%);box-shadow:0 3px 12px color-mix(in oklab, var(--panel-text) 10%, transparent),inset 0 2px 6px color-mix(in oklab, var(--panel-text) 20%, transparent);transition:transform .05s ease-out,background .1s}
12
+ .mobile-joystick-knob{position:absolute;top:50%;left:50%;width:40px;height:40px;border-radius:50%;background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--panel-text) 30%, transparent), color-mix(in oklab, var(--panel-text-3) 40%, transparent));border:1px solid color-mix(in oklab, var(--panel-text) 30%, transparent);transform:translate(-50%,-50%);box-shadow:0 2px 8px color-mix(in oklab, var(--panel-text) 8%, transparent),inset 0 1px 4px color-mix(in oklab, var(--panel-text) 15%, transparent);transition:transform .05s ease-out,background .1s}
13
13
  .mobile-joystick-knob.active{background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--accent) 70%, transparent), color-mix(in oklab, var(--accent) 50%, transparent));border-color:color-mix(in oklab, var(--accent) 70%, transparent)}
14
14
  .mobile-joystick-knob.look-active{background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--accent) 65%, transparent), color-mix(in oklab, var(--accent) 50%, transparent));border-color:color-mix(in oklab, var(--accent) 70%, transparent)}
15
15
  .mobile-joystick-directions{position:absolute;width:100%;height:100%;pointer-events:none;opacity:.3}
@@ -22,9 +22,9 @@ const CSS = `
22
22
  .mobile-action-btn:active,.mobile-action-btn.active{transform:scale(.92);border-color:color-mix(in oklab, var(--accent) 80%, transparent)}
23
23
  .mobile-action-btn .btn-icon{font-size:18px;line-height:1}
24
24
  .mobile-action-btn .btn-label{font-size:9px;opacity:.85;margin-top:2px}
25
- .mobile-zoom-controls{position:absolute;display:flex;flex-direction:row;gap:8px;pointer-events:auto;opacity:0;animation:fadeIn .4s ease-out forwards;animation-delay:.25s}
26
- .mobile-zoom-btn{display:flex;align-items:center;justify-content:center;font-size:20px;font-family:var(--ff-mono, ui-monospace, monospace);padding:0}
27
- .mobile-zoom-btn:active{transform:scale(.92)}
25
+ .mobile-zoom-controls{position:absolute;display:flex;flex-direction:column;gap:4px;pointer-events:auto;opacity:0;animation:fadeIn .4s ease-out forwards;animation-delay:.25s}
26
+ .mobile-zoom-btn{display:flex;align-items:center;justify-content:center;font-size:14px;font-family:var(--ff-mono, ui-monospace, monospace);padding:4px 8px;border-radius:6px;background:color-mix(in oklab, var(--panel-1) 80%, transparent);border:1px solid color-mix(in oklab, var(--rule) 50%, transparent);color:var(--panel-text);min-width:36px;min-height:36px}
27
+ .mobile-zoom-btn:active{transform:scale(.92);background:color-mix(in oklab, var(--panel-2) 80%, transparent)}
28
28
  .mobile-top-bar{position:absolute;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 20px 0 max(20px, env(safe-area-inset-left));background:linear-gradient(to bottom, color-mix(in oklab, var(--panel-text) 25%, transparent), transparent);pointer-events:none;opacity:0;animation:fadeIn .4s ease-out forwards}
29
29
  .mobile-joystick-label{position:absolute;bottom:-24px;left:50%;transform:translateX(-50%);font-size:10px;color:var(--panel-text-3);font-weight:600;text-transform:uppercase;letter-spacing:1px;white-space:nowrap;font-family:var(--ff-mono, ui-monospace, monospace)}
30
30
  /* Safe-area insets — keep controls clear of notch & home indicator */
@@ -109,17 +109,34 @@ export function createMobileControlsUI(controls) {
109
109
  container.className = 'ds-247420'
110
110
  container.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:9999;touch-action:none;user-select:none;-webkit-user-select:none;overflow:hidden;'
111
111
 
112
- const { container: moveEl, knob: moveKnob } = makeJoystick('move', '<span class="dir-up">W</span><span class="dir-down">S</span><span class="dir-left">A</span><span class="dir-right">D</span>')
112
+ // Move joystick - minimal visual (just base ring + knob, no directions/label)
113
+ const moveEl = document.createElement('div')
114
+ moveEl.className = 'mobile-joystick-container'
115
+ moveEl.id = 'move-joystick'
113
116
  moveEl.style.left = `${lay.moveLeft}px`
114
117
  moveEl.style.bottom = `${lay.moveBottom}px`
115
118
  moveEl.style.width = `${d}px`
116
119
  moveEl.style.height = `${d}px`
120
+ const moveBase = document.createElement('div')
121
+ moveBase.className = 'mobile-joystick-base'
122
+ moveBase.id = 'move-joystick-base'
123
+ const moveKnob = document.createElement('div')
124
+ moveKnob.className = 'mobile-joystick-knob'
125
+ moveKnob.id = 'move-joystick-knob'
126
+ moveBase.appendChild(moveKnob)
127
+ moveEl.appendChild(moveBase)
117
128
 
118
- const { container: lookEl, knob: lookKnob } = makeJoystick('look', '<span class="dir-up">^</span><span class="dir-down">v</span><span class="dir-left">&lt;</span><span class="dir-right">&gt;</span>')
129
+ // Look joystick - minimal invisible touch area (no visual base/knob/directions)
130
+ const lookEl = document.createElement('div')
131
+ lookEl.className = 'mobile-joystick-container'
132
+ lookEl.id = 'look-joystick'
119
133
  lookEl.style.right = `${lay.lookRight}px`
120
134
  lookEl.style.bottom = `${lay.lookBottom}px`
121
135
  lookEl.style.width = `${d}px`
122
136
  lookEl.style.height = `${d}px`
137
+ lookEl.style.background = 'transparent'
138
+ lookEl.style.border = 'none'
139
+ lookEl.style.boxShadow = 'none'
123
140
 
124
141
  // Action button cluster (kit Row-style grid).
125
142
  const btnsEl = document.createElement('div')
@@ -140,11 +157,11 @@ export function createMobileControlsUI(controls) {
140
157
  btnsEl.appendChild(useBtn)
141
158
  btnsEl.appendChild(shootBtn)
142
159
 
143
- const zr = lay.lookRight + r
160
+ // Zoom controls - top center, small buttons
144
161
  const zoomEl = document.createElement('div')
145
- zoomEl.className = 'mobile-zoom-controls row'
146
- zoomEl.style.cssText = `bottom:${res.bottomMargin}px;right:${zr}px;transform:translateX(50%);`
147
- const zs = Math.max(44, bs * 0.8)
162
+ zoomEl.className = 'mobile-zoom-controls'
163
+ zoomEl.style.cssText = `top:8px;left:50%;transform:translateX(-50%);pointer-events:auto;z-index:9999;display:flex;flex-direction:column;gap:4px;align-items:center;`
164
+ const zs = 36
148
165
  const mkZoom = (sym, action) => {
149
166
  const b = document.createElement('button')
150
167
  b.className = 'btn btn-ghost mobile-zoom-btn'
@@ -212,17 +229,11 @@ export function createMobileControlsUI(controls) {
212
229
  moveEl.style.top = 'auto'
213
230
  },
214
231
  onLookJoystickStart: (x, y) => {
215
- document.getElementById('look-joystick-base')?.classList.add('look-active')
216
- lookKnob.classList.add('look-active')
217
232
  const rect = lookEl.getBoundingClientRect()
218
233
  return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
219
234
  },
220
- onLookJoystickMove: (lx, ly) => { lookKnob.style.transform = `translate(calc(-50% + ${lx}px),calc(-50% + ${ly}px))` },
221
- onLookJoystickEnd: () => {
222
- lookKnob.style.transform = 'translate(-50%,-50%)'
223
- lookKnob.classList.remove('look-active')
224
- document.getElementById('look-joystick-base')?.classList.remove('look-active')
225
- },
235
+ onLookJoystickMove: (lx, ly) => { },
236
+ onLookJoystickEnd: () => { },
226
237
  onInteractablesChanged: targets => {
227
238
  const has = targets.size > 0
228
239
  useBtn.dataset.action = has ? 'interact' : 'reload'
@@ -239,10 +250,9 @@ export function createMobileControlsUI(controls) {
239
250
  lookEl.style.width = `${rdd}px`; lookEl.style.height = `${rdd}px`; lookEl.style.top = 'auto'
240
251
  btnsEl.style.bottom = `${l.buttonsBottomOffset}px`
241
252
  btnsEl.style.right = `${l.buttonsRightOffset}px`
242
- const zr2 = l.lookRight + rd
243
- zoomEl.style.bottom = `${rsp.bottomMargin}px`
244
- zoomEl.style.right = `${zr2}px`
245
- zoomEl.style.transform = 'translateX(50%)'
253
+ zoomEl.style.top = '8px'
254
+ zoomEl.style.left = '50%'
255
+ zoomEl.style.transform = 'translateX(-50%)'
246
256
  },
247
257
  onDestroy: () => {
248
258
  container.remove()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.505",
3
+ "version": "0.1.506",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "workspaces": [