spoint 0.1.685 → 0.1.686

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 (2) hide show
  1. package/client/app.js +22 -15
  2. package/package.json +1 -1
package/client/app.js CHANGED
@@ -905,7 +905,7 @@ const engineCtx = {
905
905
  get worldConfig() { return worldConfig }, get inputConfig() { return inputConfig },
906
906
  playerVrms: pm.playerVrms, entityAppMap, kit: _designKit,
907
907
  network: { send: msg => client.send(0x33, msg) },
908
- setInputConfig(cfg) { Object.assign(inputConfig,cfg); if (!inputConfig.pointerLock) { if (clickPrompt) clickPrompt.style.display='none'; if (document.pointerLockElement) document.exitPointerLock() } },
908
+ setInputConfig(cfg) { Object.assign(inputConfig,cfg); if (!inputConfig.pointerLock) { if (clickPrompt) clickPrompt.style.display='none'; if (document.pointerLockElement) _safeExitPointerLock() } },
909
909
  players: { getMesh: id=>pm.playerMeshes.get(id), getState: id=>pm.playerStates.get(id), getAnimator: id=>pm.playerAnimators.get(id), setExpression: (id,n,v)=>pm.setVRMExpression(id,n,v), setAiming: (id,v)=>{ const s=pm.playerStates.get(id); if (s) s._aiming=v } },
910
910
  // Pooled hit decals + hitscan tracers (roadmap #48/#93). point/normal/origin/target are [x,y,z] world-space.
911
911
  decals: { spawnDecal: (point, normal) => decalSystem.spawnDecal(point, normal), spawnTracer: (origin, target) => decalSystem.spawnTracer(origin, target) },
@@ -1941,7 +1941,7 @@ editor.onSelectionChange((id,data) => {
1941
1941
  // selectEntity's machine.send('DESELECT')) clears this client's presence for every other connected editor.
1942
1942
  editorPresence.sendPresence(id, false)
1943
1943
  })
1944
- editor.onEditModeChange(on => { cam.setEditMode(on, pm.playerMeshes.get(client.playerId)); if (on) { if (document.pointerLockElement) document.exitPointerLock(); editPanel.show(); client.send(MSG.SCENE_GRAPH,{}); client.send(MSG.LIST_APPS,{}) } else { editPanel.hide(); editorPresence.hide(); editorPresence.sendPresence(null, false) } })
1944
+ editor.onEditModeChange(on => { cam.setEditMode(on, pm.playerMeshes.get(client.playerId)); if (on) { if (document.pointerLockElement) _safeExitPointerLock(); editPanel.show(); client.send(MSG.SCENE_GRAPH,{}); client.send(MSG.LIST_APPS,{}) } else { editPanel.hide(); editorPresence.hide(); editorPresence.sendPresence(null, false) } })
1945
1945
  // Reverse sync: editor.js's own Y/Alt+P keyboard shortcuts change _gizmoSpace/_pivotMode directly
1946
1946
  // (drag math needs zero round-trip latency), so mirror the toolbar's displayed value after the fact.
1947
1947
  editor.onGizmoSpaceChange(space => { try { editPanel.setGizmoSpace(space) } catch (_) {} })
@@ -2200,11 +2200,24 @@ document.addEventListener('keydown', e => { const _mod=e.ctrlKey||e.metaKey; con
2200
2200
  // Match/Invite Friends, shown on Esc when pointer-lock exits DURING ACTIVE GAMEPLAY (not the
2201
2201
  // initial click-to-play prompt -- _hasEverLocked below distinguishes the two, since pointerlockchange
2202
2202
  // fires on both the very first click-prompt dismissal and every later in-match Esc).
2203
+ // document.exitPointerLock() can throw the same class of error as requestPointerLock() in unusual
2204
+ // document/element states (observed live: "The root document of this element is not valid for
2205
+ // pointer lock" from an exitPointerLock() call, not just request). 5 call sites across this file
2206
+ // called it raw; centralize the guard here so every future call site gets it for free.
2207
+ function _safeExitPointerLock() {
2208
+ try { document.exitPointerLock() } catch (e) { console.warn('[input] exitPointerLock failed:', e?.message || e) }
2209
+ }
2210
+ function _safeRequestPointerLock() {
2211
+ try {
2212
+ const p = renderer.domElement.requestPointerLock()
2213
+ if (p && typeof p.catch === 'function') p.catch(e => console.warn('[input] requestPointerLock rejected:', e?.message || e))
2214
+ } catch (e) { console.warn('[input] requestPointerLock failed:', e?.message || e) }
2215
+ }
2203
2216
  const settingsMenu = createSettingsMenu({ getCam: () => cam, getRenderer: () => renderer })
2204
2217
  if (window.__app) window.__app.settingsMenu = settingsMenu
2205
2218
  let _hasEverLocked = false
2206
2219
  const pauseMenu = createPauseMenu({
2207
- requestPointerLock: () => { try { renderer.domElement.requestPointerLock() } catch (e) { console.warn('[input] requestPointerLock failed:', e?.message || e) } },
2220
+ requestPointerLock: _safeRequestPointerLock,
2208
2221
  settingsMenu,
2209
2222
  getRoomInfo: () => _wwRoom ? { code: _wwRoom, joinLink: `${location.origin}${location.pathname}?wwjoin&room=${_wwRoom}` } : null,
2210
2223
  })
@@ -2219,7 +2232,7 @@ document.addEventListener('keydown', e => {
2219
2232
  // Editor has its own Esc-adjacent flows (deselect etc.) via editor.onKeyDown above; don't also pop
2220
2233
  // the gameplay pause menu while editing, and don't pop it over the lobby overlay either.
2221
2234
  if (clientMachine.isEditor || clientMachine.isLobby) return
2222
- if (_hasEverLocked && document.pointerLockElement === renderer.domElement) { document.exitPointerLock() }
2235
+ if (_hasEverLocked && document.pointerLockElement === renderer.domElement) { _safeExitPointerLock() }
2223
2236
  })
2224
2237
  // Spectator mode entry/exit + follow-target-cycle keybinds. O enters spectator (S/Space are already
2225
2238
  // gameplay movement keys); inside spectator: F toggles free<->follow, [ and ] cycle the follow target.
@@ -2247,7 +2260,7 @@ function _setViewportRelaxed(on) { if (_viewportMeta) _viewportMeta.setAttribute
2247
2260
  clientMachine.subscribe(() => {
2248
2261
  const wantLobby = clientMachine.isLobby
2249
2262
  if (wantLobby && !(_lobby && _lobby.isOpen)) {
2250
- if (document.pointerLockElement) document.exitPointerLock()
2263
+ if (document.pointerLockElement) _safeExitPointerLock()
2251
2264
  _setViewportRelaxed(true)
2252
2265
  // Re-check isLobby after the async import gap in case the player toggled again before it resolved.
2253
2266
  _getLobby().then(lobby => { if (clientMachine.isLobby && !lobby.isOpen) lobby.open() })
@@ -2274,7 +2287,7 @@ let _specWasSpectator = false
2274
2287
  clientMachine.subscribe(() => {
2275
2288
  const isSpec = clientMachine.isSpectator
2276
2289
  if (isSpec && !_specWasSpectator) {
2277
- if (document.pointerLockElement) document.exitPointerLock()
2290
+ if (document.pointerLockElement) _safeExitPointerLock()
2278
2291
  if (clientMachine.spectatorSubmode === 'free') cam.setEditMode(true)
2279
2292
  } else if (!isSpec && _specWasSpectator) {
2280
2293
  cam.setEditMode(false)
@@ -2426,15 +2439,9 @@ renderer.domElement.addEventListener('click', ()=>{
2426
2439
  if (!inputConfig.pointerLock || document.pointerLockElement) return
2427
2440
  // requestPointerLock() can both throw synchronously AND return a Promise that rejects
2428
2441
  // asynchronously (spec-dependent by browser/context) -- e.g. "The root document of this
2429
- // element is not valid for pointer lock". A try/catch alone only catches the synchronous
2430
- // case; the async rejection surfaces as an unhandledrejection that still trips the app's
2431
- // global error boundary, converting a benign lock-denial into a full page crash (reproduced
2432
- // live even after the sync try/catch was added). Guard both paths. The keydown fallback below
2433
- // still recovers gameplay either way.
2434
- try {
2435
- const p = renderer.domElement.requestPointerLock()
2436
- if (p && typeof p.catch === 'function') p.catch(e => console.warn('[input] requestPointerLock rejected:', e?.message || e))
2437
- } catch (e) { console.warn('[input] requestPointerLock failed:', e?.message || e) }
2442
+ // element is not valid for pointer lock". _safeRequestPointerLock guards both. The keydown
2443
+ // fallback below still recovers gameplay either way.
2444
+ _safeRequestPointerLock()
2438
2445
  })
2439
2446
  // requestPointerLock() is a trusted-gesture-gated browser API that can silently reject (denied
2440
2447
  // engagement heuristic, automation-driven click, embedded/iframe context) with no visible error --
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoint",
3
- "version": "0.1.685",
3
+ "version": "0.1.686",
4
4
  "description": "Physics and netcode SDK for multiplayer game servers",
5
5
  "type": "module",
6
6
  "workspaces": [