ugly-game 0.5.2 → 0.5.3

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.
@@ -27,6 +27,10 @@ export declare class BrowserRawBackend {
27
27
  private paused;
28
28
  constructor(opts: BrowserBackendOpts);
29
29
  setPaused(p: boolean): void;
30
+ /** Drop every held key / mouse button / touch. Called when the window can no longer observe releases (blur,
31
+ * tab hidden, pointer-lock lost) so a key held across the disruption can't stick "on" and drive the avatar
32
+ * forever. Public so a host can force it (e.g. right before opening a modal that steals focus). */
33
+ releaseHeld(): void;
30
34
  private on;
31
35
  private cursor;
32
36
  private gesture;
@@ -36,6 +36,10 @@ export class BrowserRawBackend {
36
36
  paused = false; // while a menu is open: suspend gameplay input + don't grab pointer lock
37
37
  constructor(opts) { this.target = opts.target; this.pointerLock = opts.pointerLock ?? false; this.attach(); }
38
38
  setPaused(p) { this.paused = p; }
39
+ /** Drop every held key / mouse button / touch. Called when the window can no longer observe releases (blur,
40
+ * tab hidden, pointer-lock lost) so a key held across the disruption can't stick "on" and drive the avatar
41
+ * forever. Public so a host can force it (e.g. right before opening a modal that steals focus). */
42
+ releaseHeld() { this.keysDown.clear(); this.touches.clear(); this.pointerButtons.clear(); }
39
43
  on(el, type, fn, o) {
40
44
  const handler = fn;
41
45
  el.addEventListener(type, handler, o);
@@ -54,7 +58,21 @@ export class BrowserRawBackend {
54
58
  this.on(window, 'keydown', (e) => { if (!this.keysDown.has(e.code))
55
59
  this.edges.add('key:' + e.code); this.keysDown.add(e.code); });
56
60
  this.on(window, 'keyup', (e) => { this.keysDown.delete(e.code); });
57
- this.on(window, 'blur', () => { this.keysDown.clear(); this.touches.clear(); this.pointerButtons.clear(); });
61
+ // RELEASE ALL held input whenever the window can no longer see the keys being let go. `blur` alone is not
62
+ // enough: on macOS an app-switch (Cmd+Tab) delivers the keyup to the background, and the reliable page-side
63
+ // signal is `visibilitychange`→hidden (blur can be missed). Losing pointer lock (Esc / menu) is the same
64
+ // hazard. Without this a movement key held at switch-time stays in keysDown and the avatar walks that way
65
+ // forever ("stuck always going backward"). We clear ONLY on the disruptive edge — never when the tab becomes
66
+ // visible again or when lock is ACQUIRED, which would wrongly drop a key the player is legitimately holding.
67
+ this.on(window, 'blur', () => { this.releaseHeld(); });
68
+ const onHidden = () => { if (document.hidden)
69
+ this.releaseHeld(); };
70
+ const onUnlock = () => { if (document.pointerLockElement !== this.target)
71
+ this.releaseHeld(); };
72
+ document.addEventListener('visibilitychange', onHidden);
73
+ document.addEventListener('pointerlockchange', onUnlock);
74
+ this.disposers.push(() => { document.removeEventListener('visibilitychange', onHidden); });
75
+ this.disposers.push(() => { document.removeEventListener('pointerlockchange', onUnlock); });
58
76
  this.on(this.target, 'wheel', (e) => { this.pwheel += e.deltaY; e.preventDefault(); }, { passive: false });
59
77
  this.on(this.target, 'contextmenu', (e) => { e.preventDefault(); });
60
78
  this.on(this.target, 'pointerdown', (e) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ugly-game",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {