Back to Poly Dogfight
SOURCE / PINNED RELEASE

Made of little things.

Poly Dogfight

Release
ad11a55ec88c…
Author-recorded commit
483e0e0c5000…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/poly-dogfight

Archive hash verified: fbf8fc78f64d293a…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/gamepad-source.ts
/**
 * NAP-GAMEPAD source for the controller helper. The runtime owns the only
 * native reader and pushes focus-scoped snapshots; without the optional domain
 * the helper falls back to the browser Gamepad API.
 */
import type { GamepadApi, GamepadUnavailableReason } from '@napplet/sdk';
import type { GamepadSample } from './gamepad.ts';

export type GamepadSource = {
  read: () => ArrayLike<GamepadSample | null>;
  active: () => boolean;
  dispose: () => void;
};

export function nappletGamepadSource(
  api: GamepadApi,
  visible: () => boolean = () => typeof document === 'undefined' || !document.hidden,
): GamepadSource {
  // Only snapshots carry the unavailable reason; the first use subscribes.
  let reason: GamepadUnavailableReason | undefined;
  const subscription = api.onChange((state) => {
    reason = state.available ? undefined : state.reason;
  });
  return {
    read() {
      if (!api.available) {
        const error = new Error(`Controllers ${reason ?? 'unavailable'} by the runtime`);
        // The helper reports a SecurityError as 'blocked', like the native API.
        error.name = reason === 'blocked' ? 'SecurityError' : 'NotSupportedError';
        throw error;
      }
      return api.getGamepads();
    },
    // The shell decides input focus and sends neutral values elsewhere; the
    // napplet frame need not hold DOM focus to receive live controller input.
    active: () => api.focused && visible(),
    dispose: () => subscription.close(),
  };
}