Add EMF field mode, Armory shop/waitlist, and ambient haunting layer

Three self-contained features, verified complete and cross-wired
end-to-end (audited: backend 54/54 tests, frontend 110/110 tests,
tsc --noEmit clean, i18n coverage script clean):

- EMF mode: DeviceMotion/DeviceOrientation-based field-meter sensing,
  a fifth séance channel alongside Wire/EVP/Radio/Ouija, with its own
  fragment prompt persona and full frontend gauge UI.
- Armory (shop/waitlist): pre-order capture page for the future
  Ultimate Quantum Box hardware line, rate-limited public endpoint,
  explicitly no payment collection.
- Haunting layer: ambient possession effects (dread-bed audio, title
  glitching, idle-paced whispers/manifests), respects
  prefers-reduced-motion, mounted once at the app root.

Plus WebUSB robustness fixes in lib/sdr.ts (Terratec vendor ID,
explicit selectConfiguration, isSecureContext gate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013PphXq1s43DNRj1uWKGXof
This commit is contained in:
Indiana
2026-07-21 02:28:14 +00:00
parent c372427ced
commit 0756e677b9
33 changed files with 3148 additions and 48 deletions

View File

@@ -29,6 +29,9 @@ export type SweepCallbacks = {
// USB identification for RTL2832U dongles.
export const RTL2832U_VENDOR = 0x0bda
export const RTL2832U_PRODUCTS = [0x2832, 0x2834, 0x2838, 0x2837]
// Terratec (and other rebadged) dongles share the RTL2832U guts but report a
// different vendor id — they speak the same protocol once claimed.
export const TERRATEC_VENDOR = 0x0ccd
// Request types used by librtlsdr.
const CTRL_IN = 0xc0
@@ -38,8 +41,14 @@ const USB_EPA = 0x02
const SYS = 0x09
const PAGE_USB = 0x01
/** WebUSB only exists in secure contexts (https, or localhost). */
export function isSecureContext(): boolean {
return typeof window !== 'undefined' && window.isSecureContext === true
}
export function isSupported(): boolean {
return (
isSecureContext() &&
typeof navigator !== 'undefined' &&
'usb' in navigator &&
typeof navigator.usb?.requestDevice === 'function'
@@ -62,12 +71,9 @@ export class RtlSdr {
if (!isSupported()) {
throw new Error('WebUSB is not available in this vessel (Chromium required)')
}
const filters = [
{ vendorId: RTL2832U_VENDOR, productId: 0x2832 },
{ vendorId: RTL2832U_VENDOR, productId: 0x2834 },
{ vendorId: RTL2832U_VENDOR, productId: 0x2838 },
{ vendorId: RTL2832U_VENDOR, productId: 0x2837 },
]
// Vendor-only filters: many dongles report product ids outside the
// handful we know, so filtering by productId hides them from the picker.
const filters = [{ vendorId: RTL2832U_VENDOR }, { vendorId: TERRATEC_VENDOR }]
this.device = await navigator.usb.requestDevice({ filters })
}
@@ -78,6 +84,17 @@ export class RtlSdr {
this.sampleRate = sampleRateHz
await dev.open()
// WebUSB populates `configuration` (interfaces, endpoints) only after a
// configuration is explicitly selected — without this, most dongles
// report a null configuration and everything downstream fails.
try {
await dev.selectConfiguration(1)
} catch (err) {
// Tolerable only if the device is already configured (some platforms
// refuse to re-select the active configuration).
if (!dev.configuration) throw err
}
// Find the first bulk-IN endpoint.
const iface = dev.configuration?.interfaces[0]
const alt = iface?.alternates[0]