{"_id":"@air-jam/sdk","_rev":"8-23bdfcb559d5fa7c450f4af9d395e088","name":"@air-jam/sdk","dist-tags":{"latest":"0.9.2"},"versions":{"0.1.0":{"name":"@air-jam/sdk","version":"0.1.0","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.1.0","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/air-jam#readme","bugs":{"url":"https://github.com/vucinatim/air-jam/issues"},"dist":{"shasum":"de3a03cd7d143ee4cc40d50fbf41304354437e1e","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.1.0.tgz","fileCount":56,"integrity":"sha512-aNV4UwjDMWW7L/tFObN/lAiTq5CikepAAlO5YYCP8gliFZDqwyTMpEVH97pyUTLzAtW7YJd1zRZwWoOi1350yQ==","signatures":[{"sig":"MEUCIQDjeazVdIpS7ahwHcZ58hdZKBqMNPsFnNdaW3zisf2j3gIgegiankAsazkfDpYQRiaohADqAiNzbx4JIhzsi9DW4mw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1266474},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./styles.css":"./src/styles.css"},"gitHead":"4c01dbf6077508dabd2afffeaa54b66bd2deaa87","private":false,"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"vucinatim","email":"tim.vucina@gmail.com"},"repository":{"url":"git+https://github.com/vucinatim/air-jam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"10.8.2","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"20.19.0","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","html5-qrcode":"^2.3.8","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-tabs":"^1.1.13","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.0_1766664646062_0.28261085233716643","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@air-jam/sdk","version":"0.1.1","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.1.1","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/air-jam#readme","bugs":{"url":"https://github.com/vucinatim/air-jam/issues"},"dist":{"shasum":"68910374bf46925b03840615902b075f0027cdf4","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.1.1.tgz","fileCount":62,"integrity":"sha512-lMcMB8CB/LQXP0gY+3vvS9ICE/bilRnb9O5vb3FpdIcmkduXLuKIM4C8X7ICDVLai6FKT+SuwGefdndOwY8ZsA==","signatures":[{"sig":"MEQCIFvZ2JaWxeICpq+HLw2ptNQTJnrF9H7DHOgwD0WxxajIAiAblTTvGpDneinv5B38I6KZCZrhAbFbmm5c7T7sJYy2aQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1382249},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./styles.css":"./src/styles.css"},"gitHead":"ae2645370c2ffc38e9a59bd09094f2648da30d20","private":false,"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"vucinatim","email":"tim.vucina@gmail.com"},"repository":{"url":"git+https://github.com/vucinatim/air-jam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"10.8.2","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"20.19.0","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","html5-qrcode":"^2.3.8","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-tabs":"^1.1.13","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.1_1766762367434_0.7635647263950371","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@air-jam/sdk","version":"0.1.2","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.1.2","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/air-jam#readme","bugs":{"url":"https://github.com/vucinatim/air-jam/issues"},"dist":{"shasum":"332cc56fa9dd322e326385def156d455755808ae","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.1.2.tgz","fileCount":62,"integrity":"sha512-NwK8fZmE+wY5QFVvzRW2rUqSuM8GKSbkhOyKGgYpqttPmhsaZH+EeW2eJn4KXOT4fk5P+tkleFN74ZbtcdtOEg==","signatures":[{"sig":"MEUCICkww2JcPSe6ol0GRSKwJoES30CTItPx4TQk0ZgVW/LRAiEA5eMhavtVDmkl5hxCnakiv0rGYUEQXB5tNSByMWl2ET0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1409432},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./styles.css":"./src/styles.css"},"gitHead":"d2ee72e00dd7598ad10b414906af66ec1ebf923e","private":false,"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"vucinatim","email":"tim.vucina@gmail.com"},"repository":{"url":"git+https://github.com/vucinatim/air-jam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"10.8.2","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"20.19.0","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","html5-qrcode":"^2.3.8","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-tabs":"^1.1.13","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.2_1767450489730_0.6189673034797061","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@air-jam/sdk","version":"0.1.3","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.1.3","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/air-jam#readme","bugs":{"url":"https://github.com/vucinatim/air-jam/issues"},"dist":{"shasum":"0530a23d822a57bda977aa669fd2d6a2a7e6cdbc","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.1.3.tgz","fileCount":60,"integrity":"sha512-PuzEnP5V0GPejOkV6kI5T6zEVEkuHjJKbkv2Mf2ZMqli3/LvaOfe39hvh6p6/as74+8aUlZGQM3sGkYZqMoBPA==","signatures":[{"sig":"MEUCICXStWaCgekg1Xc+1ZxfIu3DtA+01lGs3YhP6LrzknVKAiEA3c5r0tXGjx7VqzZXk6YSYOtx0EzDzBh4ZuaEfkcIItA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1369577},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./styles.css":"./src/styles.css"},"gitHead":"8ad56657e0754af497c25433bc20acb4f4a6667f","private":false,"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"vucinatim","email":"tim.vucina@gmail.com"},"repository":{"url":"git+https://github.com/vucinatim/air-jam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"10.8.2","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"20.19.0","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","html5-qrcode":"^2.3.8","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-tabs":"^1.1.13","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.3_1767459051546_0.8867801760769498","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@air-jam/sdk","version":"0.1.4","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.1.4","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/air-jam#readme","bugs":{"url":"https://github.com/vucinatim/air-jam/issues"},"dist":{"shasum":"986fa35ab0b1fd4a432906be502750a3db715a10","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.1.4.tgz","fileCount":60,"integrity":"sha512-Egkzw6VXbYqhEBcLeZ8GNIZWISQgfSlOmsTLJ2gif8LW820kUDQKFAfQVY9fPtr/wBz5xQk0B01XvGuledBhlw==","signatures":[{"sig":"MEYCIQCcbf4s6zEYkANmnBeq8719JmJs6iOtLhofuoDNhuNh7QIhALjUQ0QFzyiFJaAQg2BcAm/APFbyvdqmO1G3OBI1j6K+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1372481},"main":"./dist/index.cjs","type":"module","_from":"file:air-jam-sdk-0.1.4.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./styles.css":"./src/styles.css"},"private":false,"scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"vucinatim","email":"tim.vucina@gmail.com"},"_resolved":"/private/var/folders/1n/6vf57tzs309crksprtspcc6w0000gn/T/1cd03c4a9cfd9518d2c622bec7df64e3/air-jam-sdk-0.1.4.tgz","_integrity":"sha512-Egkzw6VXbYqhEBcLeZ8GNIZWISQgfSlOmsTLJ2gif8LW820kUDQKFAfQVY9fPtr/wBz5xQk0B01XvGuledBhlw==","repository":{"url":"git+https://github.com/vucinatim/air-jam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"10.8.2","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"20.19.0","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","html5-qrcode":"^2.3.8","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-tabs":"^1.1.13","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.1.4_1773262718721_0.08859609042033956","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@air-jam/sdk","version":"0.9.0","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.9.0","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/airjam#readme","bugs":{"url":"https://github.com/vucinatim/airjam/issues"},"dist":{"shasum":"21f890a5977a8d5ccac1eb82561dd16663868e71","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.9.0.tgz","fileCount":340,"integrity":"sha512-IbuMlkqORVQS8wOM1/DuN2zpHBvYmGxXNVi/P08v3+LhZQslnb4kGgLrDgkNRX2E3qLhiAvMs2dq+Vg8WdpSrA==","signatures":[{"sig":"MEQCIH2eHR7pVjgHMVwuKo2uyrhTh6YpHRJo5LZftXjqIcOuAiAXCcZ2yyDexpBegDwyAKOiuGxaWNWwq5cxrkq1U4wtPQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@air-jam%2fsdk@0.9.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":5023738},"main":"./dist/index.cjs","type":"module","_from":"file:air-jam-sdk-0.9.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./ui":{"types":"./dist/ui.d.ts","import":"./dist/ui.js","require":"./dist/ui.cjs"},"./arcade":{"types":"./dist/arcade.d.ts","import":"./dist/arcade.js","require":"./dist/arcade.cjs"},"./prefabs":{"types":"./dist/prefabs.d.ts","import":"./dist/prefabs.js","require":"./dist/prefabs.cjs"},"./preview":{"types":"./dist/preview.d.ts","import":"./dist/preview.js","require":"./dist/preview.cjs"},"./release":{"types":"./dist/release.d.ts","import":"./dist/release.js","require":"./dist/release.cjs"},"./metadata":{"types":"./dist/metadata.d.ts","import":"./dist/metadata.js","require":"./dist/metadata.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./arcade/url":{"types":"./dist/arcade/url.d.ts","import":"./dist/arcade/url.js","require":"./dist/arcade/url.cjs"},"./styles.css":"./src/styles.css","./arcade/host":{"types":"./dist/arcade/host.d.ts","import":"./dist/arcade/host.js","require":"./dist/arcade/host.cjs"},"./capabilities":{"types":"./dist/capabilities.d.ts","import":"./dist/capabilities.js","require":"./dist/capabilities.cjs"},"./agent-tooling":{"types":"./dist/agent-tooling.d.ts","import":"./dist/agent-tooling.js","require":"./dist/agent-tooling.cjs"},"./arcade/bridge":{"types":"./dist/arcade/bridge.d.ts","import":"./dist/arcade/bridge.js","require":"./dist/arcade/bridge.cjs"},"./arcade/surface":{"types":"./dist/arcade/surface.d.ts","import":"./dist/arcade/surface.js","require":"./dist/arcade/surface.cjs"},"./platform-machine":{"types":"./dist/platform-machine.d.ts","import":"./dist/platform-machine.js","require":"./dist/platform-machine.cjs"},"./arcade/bridge/host":{"types":"./dist/arcade/bridge/host.d.ts","import":"./dist/arcade/bridge/host.js","require":"./dist/arcade/bridge/host.cjs"},"./arcade/bridge/iframe":{"types":"./dist/arcade/bridge/iframe.d.ts","import":"./dist/arcade/bridge/iframe.js","require":"./dist/arcade/bridge/iframe.cjs"},"./arcade/bridge/controller":{"types":"./dist/arcade/bridge/controller.d.ts","import":"./dist/arcade/bridge/controller.js","require":"./dist/arcade/bridge/controller.cjs"}},"private":false,"scripts":{"dev":"tsup --watch --no-clean","test":"vitest run --config vitest.config.ts","build":"tsup","typecheck":"tsc --noEmit && tsc -p tests/tsconfig.json --noEmit","test:watch":"vitest --config vitest.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2390914d-2516-4ad7-88f8-d5b64d73ccc5"}},"_resolved":"/tmp/969f7dd903463b120f8ce12582346384/air-jam-sdk-0.9.0.tgz","_integrity":"sha512-IbuMlkqORVQS8wOM1/DuN2zpHBvYmGxXNVi/P08v3+LhZQslnb4kGgLrDgkNRX2E3qLhiAvMs2dq+Vg8WdpSrA==","repository":{"url":"git+https://github.com/vucinatim/airjam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"11.13.0","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1","@air-jam/runtime-topology":"^0.9.0"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"ui":["dist/ui.d.ts"],"arcade":["dist/arcade.d.ts"],"prefabs":["dist/prefabs.d.ts"],"preview":["dist/preview.d.ts"],"release":["dist/release.d.ts"],"metadata":["dist/metadata.d.ts"],"protocol":["dist/protocol.d.ts"],"arcade/url":["dist/arcade/url.d.ts"],"arcade/host":["dist/arcade/host.d.ts"],"capabilities":["dist/capabilities.d.ts"],"agent-tooling":["dist/agent-tooling.d.ts"],"arcade/bridge":["dist/arcade/bridge.d.ts"],"arcade/surface":["dist/arcade/surface.d.ts"],"platform-machine":["dist/platform-machine.d.ts"],"arcade/bridge/host":["dist/arcade/bridge/host.d.ts"],"arcade/bridge/iframe":["dist/arcade/bridge/iframe.d.ts"],"arcade/bridge/controller":["dist/arcade/bridge/controller.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.1","vitest":"^3.2.4","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.9.0_1777926287504_0.17483243610179922","host":"s3://npm-registry-packages-npm-production"}},"0.9.1":{"name":"@air-jam/sdk","version":"0.9.1","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"license":"MIT","_id":"@air-jam/sdk@0.9.1","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"homepage":"https://github.com/vucinatim/airjam#readme","bugs":{"url":"https://github.com/vucinatim/airjam/issues"},"dist":{"shasum":"05b86162716381d4ca3c33bc384c7a54b22f7488","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.9.1.tgz","fileCount":349,"integrity":"sha512-90R1016uZTC0S2oYhJeCH0XdLs58E16Hz0QgsNGWyb3phNSzD7zmoow2o+TYs2UW6rnsqMXYN9oqTIKGbgHQpw==","signatures":[{"sig":"MEQCIB1Wnn31pNl55/yOTTizVWeAtx7OGvqfDblnnpFdrZHaAiAtic08IuMaXg9TuRhU2g0DNa2LxiZo8PR11i3dy+Rf0A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@air-jam%2fsdk@0.9.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":5121291},"main":"./dist/index.cjs","type":"module","_from":"file:air-jam-sdk-0.9.1.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./ui":{"types":"./dist/ui.d.ts","import":"./dist/ui.js","require":"./dist/ui.cjs"},"./arcade":{"types":"./dist/arcade.d.ts","import":"./dist/arcade.js","require":"./dist/arcade.cjs"},"./prefabs":{"types":"./dist/prefabs.d.ts","import":"./dist/prefabs.js","require":"./dist/prefabs.cjs"},"./preview":{"types":"./dist/preview.d.ts","import":"./dist/preview.js","require":"./dist/preview.cjs"},"./release":{"types":"./dist/release.d.ts","import":"./dist/release.js","require":"./dist/release.cjs"},"./metadata":{"types":"./dist/metadata.d.ts","import":"./dist/metadata.js","require":"./dist/metadata.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./arcade/url":{"types":"./dist/arcade/url.d.ts","import":"./dist/arcade/url.js","require":"./dist/arcade/url.cjs"},"./styles.css":"./src/styles.css","./arcade/host":{"types":"./dist/arcade/host.d.ts","import":"./dist/arcade/host.js","require":"./dist/arcade/host.cjs"},"./capabilities":{"types":"./dist/capabilities.d.ts","import":"./dist/capabilities.js","require":"./dist/capabilities.cjs"},"./agent-tooling":{"types":"./dist/agent-tooling.d.ts","import":"./dist/agent-tooling.js","require":"./dist/agent-tooling.cjs"},"./arcade/bridge":{"types":"./dist/arcade/bridge.d.ts","import":"./dist/arcade/bridge.js","require":"./dist/arcade/bridge.cjs"},"./arcade/surface":{"types":"./dist/arcade/surface.d.ts","import":"./dist/arcade/surface.js","require":"./dist/arcade/surface.cjs"},"./platform-machine":{"types":"./dist/platform-machine.d.ts","import":"./dist/platform-machine.js","require":"./dist/platform-machine.cjs"},"./runtime-topology":{"types":"./dist/runtime-topology.d.ts","import":"./dist/runtime-topology.js","require":"./dist/runtime-topology.cjs"},"./arcade/bridge/host":{"types":"./dist/arcade/bridge/host.d.ts","import":"./dist/arcade/bridge/host.js","require":"./dist/arcade/bridge/host.cjs"},"./arcade/bridge/iframe":{"types":"./dist/arcade/bridge/iframe.d.ts","import":"./dist/arcade/bridge/iframe.js","require":"./dist/arcade/bridge/iframe.cjs"},"./arcade/bridge/controller":{"types":"./dist/arcade/bridge/controller.d.ts","import":"./dist/arcade/bridge/controller.js","require":"./dist/arcade/bridge/controller.cjs"}},"private":false,"scripts":{"dev":"tsup --watch --no-clean","test":"vitest run --config vitest.config.ts","build":"tsup","typecheck":"tsc --noEmit && tsc -p tests/tsconfig.json --noEmit","test:watch":"vitest --config vitest.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2390914d-2516-4ad7-88f8-d5b64d73ccc5"}},"_resolved":"/tmp/7b442414c679c65f49b96868be828568/air-jam-sdk-0.9.1.tgz","_integrity":"sha512-90R1016uZTC0S2oYhJeCH0XdLs58E16Hz0QgsNGWyb3phNSzD7zmoow2o+TYs2UW6rnsqMXYN9oqTIKGbgHQpw==","repository":{"url":"git+https://github.com/vucinatim/airjam.git","type":"git","directory":"packages/sdk"},"_npmVersion":"11.13.0","description":"The core SDK for building Air Jam games and controllers","directories":{},"_nodeVersion":"24.14.1","dependencies":{"zod":"^4.2.1","clsx":"^2.1.1","howler":"^2.2.4","nanoid":"^5.1.6","qrcode":"^1.5.4","zustand":"^5.0.3","lucide-react":"^0.554.0","tailwind-merge":"^2.5.5","socket.io-client":"^4.8.1","@radix-ui/react-slot":"^1.2.4","@radix-ui/react-slider":"^1.3.6","class-variance-authority":"^0.7.1"},"publishConfig":{"access":"public"},"typesVersions":{"*":{"ui":["dist/ui.d.ts"],"arcade":["dist/arcade.d.ts"],"prefabs":["dist/prefabs.d.ts"],"preview":["dist/preview.d.ts"],"release":["dist/release.d.ts"],"metadata":["dist/metadata.d.ts"],"protocol":["dist/protocol.d.ts"],"arcade/url":["dist/arcade/url.d.ts"],"arcade/host":["dist/arcade/host.d.ts"],"capabilities":["dist/capabilities.d.ts"],"agent-tooling":["dist/agent-tooling.d.ts"],"arcade/bridge":["dist/arcade/bridge.d.ts"],"arcade/surface":["dist/arcade/surface.d.ts"],"platform-machine":["dist/platform-machine.d.ts"],"runtime-topology":["dist/runtime-topology.d.ts"],"arcade/bridge/host":["dist/arcade/bridge/host.d.ts"],"arcade/bridge/iframe":["dist/arcade/bridge/iframe.d.ts"],"arcade/bridge/controller":["dist/arcade/bridge/controller.d.ts"]}},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.5.1","jsdom":"^29.0.1","vitest":"^3.2.4","typescript":"~5.9.3","tailwindcss":"^4.1.17","@types/react":"^19.2.5","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react-dom":"^19.2.3","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_0.9.1_1778004721965_0.6003272781301419","host":"s3://npm-registry-packages-npm-production"}},"0.9.2":{"name":"@air-jam/sdk","version":"0.9.2","description":"The core SDK for building Air Jam games and controllers","private":false,"type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/vucinatim/airjam.git","directory":"packages/sdk"},"homepage":"https://github.com/vucinatim/airjam#readme","bugs":{"url":"https://github.com/vucinatim/airjam/issues"},"publishConfig":{"access":"public"},"keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./ui":{"types":"./dist/ui.d.ts","import":"./dist/ui.js","require":"./dist/ui.cjs"},"./arcade":{"types":"./dist/arcade.d.ts","import":"./dist/arcade.js","require":"./dist/arcade.cjs"},"./arcade/bridge":{"types":"./dist/arcade/bridge.d.ts","import":"./dist/arcade/bridge.js","require":"./dist/arcade/bridge.cjs"},"./arcade/bridge/controller":{"types":"./dist/arcade/bridge/controller.d.ts","import":"./dist/arcade/bridge/controller.js","require":"./dist/arcade/bridge/controller.cjs"},"./arcade/bridge/host":{"types":"./dist/arcade/bridge/host.d.ts","import":"./dist/arcade/bridge/host.js","require":"./dist/arcade/bridge/host.cjs"},"./arcade/bridge/iframe":{"types":"./dist/arcade/bridge/iframe.d.ts","import":"./dist/arcade/bridge/iframe.js","require":"./dist/arcade/bridge/iframe.cjs"},"./arcade/host":{"types":"./dist/arcade/host.d.ts","import":"./dist/arcade/host.js","require":"./dist/arcade/host.cjs"},"./arcade/surface":{"types":"./dist/arcade/surface.d.ts","import":"./dist/arcade/surface.js","require":"./dist/arcade/surface.cjs"},"./arcade/url":{"types":"./dist/arcade/url.d.ts","import":"./dist/arcade/url.js","require":"./dist/arcade/url.cjs"},"./protocol":{"types":"./dist/protocol.d.ts","import":"./dist/protocol.js","require":"./dist/protocol.cjs"},"./capabilities":{"types":"./dist/capabilities.d.ts","import":"./dist/capabilities.js","require":"./dist/capabilities.cjs"},"./metadata":{"types":"./dist/metadata.d.ts","import":"./dist/metadata.js","require":"./dist/metadata.cjs"},"./prefabs":{"types":"./dist/prefabs.d.ts","import":"./dist/prefabs.js","require":"./dist/prefabs.cjs"},"./preview":{"types":"./dist/preview.d.ts","import":"./dist/preview.js","require":"./dist/preview.cjs"},"./release":{"types":"./dist/release.d.ts","import":"./dist/release.js","require":"./dist/release.cjs"},"./platform-machine":{"types":"./dist/platform-machine.d.ts","import":"./dist/platform-machine.js","require":"./dist/platform-machine.cjs"},"./agent-tooling":{"types":"./dist/agent-tooling.d.ts","import":"./dist/agent-tooling.js","require":"./dist/agent-tooling.cjs"},"./runtime-topology":{"types":"./dist/runtime-topology.d.ts","import":"./dist/runtime-topology.js","require":"./dist/runtime-topology.cjs"},"./styles.css":"./src/styles.css"},"typesVersions":{"*":{"ui":["dist/ui.d.ts"],"arcade":["dist/arcade.d.ts"],"arcade/bridge":["dist/arcade/bridge.d.ts"],"arcade/bridge/controller":["dist/arcade/bridge/controller.d.ts"],"arcade/bridge/host":["dist/arcade/bridge/host.d.ts"],"arcade/bridge/iframe":["dist/arcade/bridge/iframe.d.ts"],"arcade/host":["dist/arcade/host.d.ts"],"arcade/surface":["dist/arcade/surface.d.ts"],"arcade/url":["dist/arcade/url.d.ts"],"protocol":["dist/protocol.d.ts"],"capabilities":["dist/capabilities.d.ts"],"metadata":["dist/metadata.d.ts"],"prefabs":["dist/prefabs.d.ts"],"release":["dist/release.d.ts"],"platform-machine":["dist/platform-machine.d.ts"],"agent-tooling":["dist/agent-tooling.d.ts"],"runtime-topology":["dist/runtime-topology.d.ts"],"preview":["dist/preview.d.ts"]}},"dependencies":{"@radix-ui/react-slider":"^1.3.6","@radix-ui/react-slot":"^1.2.4","class-variance-authority":"^0.7.1","clsx":"^2.1.1","howler":"^2.2.4","lucide-react":"^0.554.0","nanoid":"^5.1.6","qrcode":"^1.5.4","socket.io-client":"^4.8.1","tailwind-merge":"^2.5.5","zod":"^4.2.1","zustand":"^5.0.3"},"peerDependencies":{"react":"^19.2.0","react-dom":"^19.2.0"},"devDependencies":{"@testing-library/react":"^16.3.2","@types/howler":"^2.2.12","@types/qrcode":"^1.5.5","@types/react":"^19.2.5","@types/react-dom":"^19.2.3","jsdom":"^29.0.1","tailwindcss":"^4.1.17","tsup":"^8.5.1","typescript":"~5.9.3","vitest":"^3.2.4"},"scripts":{"build":"tsup","test":"vitest run --config vitest.config.ts","test:watch":"vitest --config vitest.config.ts","typecheck":"tsc --noEmit && tsc -p tests/tsconfig.json --noEmit","dev":"tsup --watch --no-clean"},"_id":"@air-jam/sdk@0.9.2","_integrity":"sha512-7nrsfxd8T/cfKuypBP40ulgJJLZyPAIBfMtNHL2JTUSANSXNMKhY9H99fbtknpu5qyEUvuUXxI/uFnvFfzaiuA==","_resolved":"/tmp/454f8390fb9b7a1535230fb84a20b539/air-jam-sdk-0.9.2.tgz","_from":"file:air-jam-sdk-0.9.2.tgz","_nodeVersion":"24.14.1","_npmVersion":"11.13.0","dist":{"integrity":"sha512-7nrsfxd8T/cfKuypBP40ulgJJLZyPAIBfMtNHL2JTUSANSXNMKhY9H99fbtknpu5qyEUvuUXxI/uFnvFfzaiuA==","shasum":"3c7ca3e976b5f8fef789c8ad3f22952d130cae3b","tarball":"https://registry.npmjs.org/@air-jam/sdk/-/sdk-0.9.2.tgz","fileCount":350,"unpackedSize":5152861,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@air-jam%2fsdk@0.9.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICQPO14uKurs9KbG+thjWwpYPfW9IcbrfzIf0SC9ahCQAiA8QNpfmG/4s/RO+uBMUbn5EI7Q9D1BB0PrHsG9MXVFPQ=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2390914d-2516-4ad7-88f8-d5b64d73ccc5"}},"directories":{},"maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_0.9.2_1778009388947_0.12192878662519324"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-25T12:10:45.852Z","modified":"2026-05-05T19:29:49.539Z","0.1.0":"2025-12-25T12:10:46.219Z","0.1.1":"2025-12-26T15:19:27.621Z","0.1.2":"2026-01-03T14:28:09.923Z","0.1.3":"2026-01-03T16:50:51.775Z","0.1.4":"2026-03-11T20:58:38.927Z","0.9.0":"2026-05-04T20:24:47.713Z","0.9.1":"2026-05-05T18:12:02.299Z","0.9.2":"2026-05-05T19:29:49.171Z"},"bugs":{"url":"https://github.com/vucinatim/airjam/issues"},"license":"MIT","homepage":"https://github.com/vucinatim/airjam#readme","keywords":["air-jam","game","multiplayer","sdk","react","typescript","controller","mobile-controller","real-time","socket.io","gaming"],"repository":{"type":"git","url":"git+https://github.com/vucinatim/airjam.git","directory":"packages/sdk"},"description":"The core SDK for building Air Jam games and controllers","maintainers":[{"name":"vucinatim","email":"tim.vucina@gmail.com"}],"readme":"# @air-jam/sdk\n\nCore SDK for building Air Jam hosts and mobile controllers.\n\n## Stability And Compatibility Policy\n\nAir Jam SDK follows semver. What that means concretely for creators building\nagainst v1:\n\n1. **Durable v1 surface.** `@air-jam/sdk`, `@air-jam/sdk/ui`, and\n   `@air-jam/sdk/styles.css` are the stable authoring and UI lanes. Breaking\n   changes on these require a major version bump, and v1 will be kept working\n   for games for **at least 12 months** after v1.0.0 ships.\n2. **Non-breaking v1.x.** Within the `1.x` line we commit to: no removals,\n   no incompatible type changes, and no silent behavior changes on the stable\n   lanes above. New functionality lands as additive APIs.\n3. **Experimental leaves.** `@air-jam/sdk/preview`, `@air-jam/sdk/arcade*`,\n   `@air-jam/sdk/protocol`, `@air-jam/sdk/capabilities`,\n   `@air-jam/sdk/metadata`, and `@air-jam/sdk/prefabs` are intentionally\n   unstable future-facing seams. They may change within `1.x` — each carries\n   a documented purpose in its leaf, and changes are noted in release notes.\n   The agent-facing `runtime-control`, `runtime-inspection`,\n   `runtime-observability`, and `contracts/v2` seams exist in-source but are\n   **not exported publicly** until a first-party consumer lands; they will be\n   re-exported as explicit experimental leaves when that happens.\n4. **v2 migration.** When v2 ships, we commit to publishing a codemod (or\n   migration notes if the surface is too narrow to automate) alongside the\n   release. v1 games will not be silently broken — the v1 SDK will remain\n   installable.\n5. **Metadata and capability contracts.** The `defineAirJamGameMetadata` and\n   `defineAirJamGameCapabilities` helpers produce versioned, frozen objects.\n   Contract versions are bumped explicitly so the platform can accept mixed\n   versions during a transition window.\n\nGames built against v1 should declare `supportedSdkRange: \"^1.0.0\"` in their\nmetadata unless they intentionally pin tighter.\n\n## Three Lanes (Canonical)\n\n1. `Input lane` (high frequency, transient):\n   `useControllerTick` + `useInputWriter` on controller, `host.getInput` / `useGetInput` on host.\n2. `State lane` (replicated, host-authoritative):\n   `createAirJamStore` + `useActions` with `(ctx, payload)` action handlers.\n3. `Signal lane` (out-of-band UX/system):\n   `sendSignal` / `sendSystemCommand` for haptics, toasts, room-level commands, and remote audio cues.\n\nDo not mix lanes:\n\n1. Do not send per-frame stick/button input through store actions.\n2. Do not mutate authoritative game state via signals.\n3. Do not dispatch state actions via `state.actions.*`; use `useStore.useActions()`.\n\n## Installation\n\n```bash\npnpm add @air-jam/sdk zod\n```\n\n## Minimal Setup\n\nUse `defineAirJamGameMetadata` for catalog-facing identity and\n`createAirJamApp` for runtime/session wiring. Platform submissions can still\nedit metadata in the dashboard; the code export gives tooling a typed default\nto prefill, validate, and compare against release artifacts.\n\n```tsx\nimport { createAirJamApp, env } from \"@air-jam/sdk\";\nimport { defineAirJamGameMetadata } from \"@air-jam/sdk/metadata\";\nimport { z } from \"zod\";\n\nconst inputSchema = z.object({\n  vector: z.object({ x: z.number(), y: z.number() }),\n  action: z.boolean(),\n});\n\nexport const gameMetadata = defineAirJamGameMetadata({\n  slug: \"my-game\",\n  name: \"My Game\",\n  tagline: \"A short catalog pitch for players.\",\n  category: \"party\",\n  minPlayers: 1,\n  maxPlayers: 4,\n  inputModalities: [\"buttons\", \"touch\"],\n  supportedSdkRange: \"^1.0.0\",\n  maintainer: { name: \"Your Name\" },\n});\n\nexport const airjam = createAirJamApp({\n  runtime: env.vite(import.meta.env),\n  controllerPath: \"/controller\",\n  input: { schema: inputSchema },\n});\n```\n\n## Production Auth Modes\n\nThere are two canonical production modes:\n\n1. Static `appId` mode\n2. Optional signed host-grant mode\n\nStatic `appId` mode is the default. Set:\n\n```bash\nVITE_AIR_JAM_SERVER_URL=https://api.airjam.io\nVITE_AIR_JAM_APP_ID=aj_app_your_app_id\n```\n\nIf you want stricter ownership guarantees while keeping the game static, add:\n\n```bash\nVITE_AIR_JAM_HOST_GRANT_ENDPOINT=/api/airjam/host-grant\n```\n\nand have that endpoint return:\n\n```json\n{ \"hostGrant\": \"...\" }\n```\n\nThe SDK fetches the host grant automatically before `host:bootstrap`. Game code stays unchanged.\n\n## Host Usage\n\nMount runtime ownership explicitly at the host boundary, then read it from child code with `useAirJamHost()`.\n\nIf a component only needs host session state, call `useAirJamHost(selector)` to\navoid rerendering on unrelated runtime fields.\nSelectors receive state fields only; call `useAirJamHost()` when a component\nalso needs runtime controls such as `joinUrl`, `sendSignal`, or `getInput`.\n\n```tsx\nimport { AirJamHostRuntime, env, useAirJamHost } from \"@air-jam/sdk\";\n\nconst HostShell = () => (\n  <AirJamHostRuntime\n    topology={env.vite(import.meta.env).topology}\n    appId={import.meta.env.VITE_AIR_JAM_APP_ID}\n    input={{ schema: inputSchema }}\n    onPlayerJoin={(player) => console.log(\"joined\", player.id)}\n    onPlayerLeave={(controllerId) => console.log(\"left\", controllerId)}\n  >\n    <HostView />\n  </AirJamHostRuntime>\n);\n\nexport const HostView = () => {\n  const host = useAirJamHost();\n  return (\n    <section>\n      <h1>Room: {host.roomId}</h1>\n      <p>Status: {host.connectionStatus}</p>\n      <p>Join URL: {host.joinUrl}</p>\n    </section>\n  );\n};\n```\n\nUse `host.players` for gameplay-facing player iteration. Use `host.controllers`\nwhen you need the richer controller-session roster with source\n(`phone | preview | virtual`), connected state, and resume-lease metadata.\nUse `host.resetRoom()` when you need a fresh empty room without restarting the\nlocal backend.\n\nUse `host.getInput(controllerId)` in your game loop.\n\nDefault input behavior:\n\n- booleans: `pulse` (tap-safe consume-on-read)\n- vectors: `latest` (continuous latest value)\n\nOptional overrides are available via `input.behavior` (`pulse | hold | latest`).\n\n### Migration From `input.latch`\n\nIf you previously used:\n\n```ts\ninput: {\n  schema,\n  latch: {\n    booleanFields: [\"action\"],\n    vectorFields: [\"vector\"],\n  },\n}\n```\n\nUse:\n\n```ts\ninput: {\n  schema,\n  behavior: {\n    pulse: [\"action\", \"vector\"],\n  },\n}\n```\n\nNotes:\n\n- most games can now remove input behavior config entirely (`input: { schema }`)\n- booleans default to tap-safe `pulse`\n- vectors default to `latest` (continuous)\n\n## Controller Usage\n\nMount runtime ownership explicitly at the controller boundary, then read it from child code with `useAirJamController()`.\n\nIf a component only needs controller session state, call\n`useAirJamController(selector)` to avoid rerendering on unrelated runtime\nupdates.\nSelectors receive state fields only; call `useAirJamController()` when a\ncomponent also needs controls such as `sendSystemCommand`.\n\n```tsx\nimport {\n  AirJamControllerRuntime,\n  env,\n  useAirJamController,\n  useControllerTick,\n  useInputWriter,\n} from \"@air-jam/sdk\";\nimport { SurfaceViewport } from \"@air-jam/sdk/ui\";\n\nconst ControllerShell = () => (\n  <AirJamControllerRuntime\n    topology={env.vite(import.meta.env).topology}\n    appId={import.meta.env.VITE_AIR_JAM_APP_ID}\n    nickname=\"Player 1\"\n  >\n    <ControllerView />\n  </AirJamControllerRuntime>\n);\n\nexport const ControllerView = () => {\n  const controller = useAirJamController();\n  const writeInput = useInputWriter();\n\n  useControllerTick(\n    () => {\n      writeInput({\n        vector: { x: 0, y: 0 },\n        action: false,\n      });\n    },\n    {\n      enabled:\n        controller.connectionStatus === \"connected\" &&\n        controller.runtimeState === \"playing\",\n      intervalMs: 16,\n    },\n  );\n\n  return (\n    <SurfaceViewport orientation=\"portrait\">\n      <section>\n        <button\n          onPointerDown={() =>\n            writeInput({\n              vector: { x: 0, y: 0 },\n              action: true, // one-shot pulses still valid\n            })\n          }\n        >\n          Action\n        </button>\n      </section>\n    </SurfaceViewport>\n  );\n};\n```\n\nControllers usually join via URL query param: `/controller?room=ABCD`.\n\nStandalone controllers also keep one stable local device identity and the last\nroom-scoped controller binding. If the same phone refreshes or briefly\ndisconnects, the SDK automatically attempts to resume that controller binding\ninstead of creating a duplicate player.\n\nThe important rule is:\n\n1. mount `AirJamHostRuntime` / `AirJamControllerRuntime` once per runtime surface\n2. use `useAirJamHost()` / `useAirJamController()` only as read hooks below that boundary\n3. wrap controller UI in `SurfaceViewport` and set its `orientation` there\n\nWhen the controller runs inside Arcade, `SurfaceViewport` automatically publishes\nits orientation to the parent Arcade chrome. The same component still handles\nstandalone controller layout, so games do not need a separate host-side\norientation bridge. Multiplayer game state should live in the networked stores\nand replicate automatically.\n\n## Preview Controllers (Experimental)\n\nThe preview-controller feature lives under the explicit experimental leaf:\n\n```ts\nimport { HostPreviewControllerWorkspace } from \"@air-jam/sdk/preview\";\n```\n\nUse it when you want a fast local desktop tryout path without replacing the\nnormal phone-controller product model.\n\nImportant rules:\n\n1. phone controllers remain the canonical product experience\n2. preview controllers are not a second topology or fake simulator path\n3. preview controllers use the real controller route and join the same room as normal controllers\n4. production should stay explicit opt-in\n\nThe preview workspace also exposes local recovery controls:\n\n1. source-aware `Kick` controls for room controllers\n2. `Reset room`, which creates a fresh empty room and reloads the local host page\n\nRecommended host usage:\n\n```tsx\nimport { HostPreviewControllerWorkspace } from \"@air-jam/sdk/preview\";\n\nexport const HostView = () => {\n  const previewControllersEnabled = import.meta.env.DEV;\n\n  return (\n    <>\n      <GameCanvas />\n      <HostPreviewControllerWorkspace enabled={previewControllersEnabled} />\n    </>\n  );\n};\n```\n\nThe shared preview leaf currently provides:\n\n1. `HostPreviewControllerWorkspace` for normal host-surface mounting\n2. `PreviewControllerWorkspace` and `PreviewControllerWindow` for lower-level composition\n3. `usePreviewControllerManager` for host-local preview session state\n4. `buildPreviewControllerUrl` and related launch helpers for explicit launch control\n\nThe floating window chrome also supports per-window portrait/landscape rotation,\nand active preview-window opacity is driven by the shared platform settings\nruntime so hosts can persist their preferred transparency level.\n\nKeep preview usage inside explicit host-side UI and do not treat it as a stable\nroot-SDK contract yet.\n\n## Runtime Contract Seams (In-Source, Not Public Exports)\n\nAir Jam also carries agent-facing runtime seams for control, inspection, and\nobservability. Those modules still exist in-source, but they are **not public\npackage exports in v1**, so consumers should not import:\n\n1. `@air-jam/sdk/runtime-control`\n2. `@air-jam/sdk/runtime-inspection`\n3. `@air-jam/sdk/runtime-observability`\n4. `@air-jam/sdk/contracts/v2`\n\nCurrent policy:\n\n1. keep these seams private until a real first-party consumer lands\n2. treat them as future agent-facing homes for bots, tests, previews, and agent tooling\n3. re-export them later as explicit experimental leaves instead of implying they are stable root-SDK contracts\n\n## Prefab Contract Leaf (Experimental)\n\nPrefab definitions should live under a stable, scanable contract instead of\ndrifting between scene glue, render layers, and runtime population code.\n\nThe first prefab contract helpers now live under the explicit experimental leaf:\n\n```ts\nimport {\n  createPrefabCatalog,\n  definePrefab,\n  type PrefabDefinition,\n} from \"@air-jam/sdk/prefabs\";\n```\n\nUse this leaf when you need a canonical prefab definition that future tooling\ncan scan, preview, configure, and catalog.\n\nImportant rules:\n\n1. this leaf defines prefab contracts, not a prefab runtime or editor\n2. keep prefab definitions separate from scene population, pooling, or runtime spawn systems\n3. keep larger gameplay behavior in domain, engine, or adapter modules instead of burying it inside prefab metadata\n4. treat this as the future-facing namespace for Studio and agent-oriented prefab tooling\n\nCurrent scope:\n\n1. `definePrefab(...)` for a stable prefab definition shape\n2. `createPrefabCatalog(...)` for a game-owned prefab registry export\n3. shared preview and placement descriptor types for future Studio/catalog work\n\n## Controller Feedback Helpers\n\nUse explicit audio ownership at each host/controller surface boundary, then consume that runtime-owned manager below it. `AudioRuntime` is role-aware: controller surfaces automatically receive host-triggered remote sounds.\n\n```tsx\nimport { AudioRuntime, useAudio, useControllerToasts } from \"@air-jam/sdk\";\n\nconst manifest = {\n  hit: { src: [\"/sounds/hit.wav\"] },\n};\n\nconst ControllerHud = () => {\n  const audio = useAudio();\n  const { latestToast } = useControllerToasts();\n\n  return latestToast ? <p>{latestToast.message}</p> : null;\n};\n\nconst ControllerShell = () => (\n  <AudioRuntime manifest={manifest}>\n    <ControllerHud />\n  </AudioRuntime>\n);\n```\n\nHost-side `sendSignal(\"TOAST\", ...)` now pairs directly with `useControllerToasts()`.\nMount `AudioRuntime` once per runtime surface, then call `useAudio()` only below that boundary.\n\n## Shared Platform Settings\n\nShared user settings are platform-owned and inherited by embedded games.\n\nMount `PlatformSettingsRuntime` once in the platform shell when you want a persisted owner runtime.\n`AirJamHostRuntime` / `AirJamControllerRuntime` already provide a settings boundary for games, so repo games should not wrap each host/controller surface in another redundant `PlatformSettingsRuntime`.\n\n```tsx\nimport {\n  PlatformSettingsRuntime,\n  useInheritedPlatformSettings,\n  usePlatformAudioSettings,\n} from \"@air-jam/sdk\";\n\nconst ArcadeShell = () => (\n  <PlatformSettingsRuntime persistence=\"local\">\n    <SettingsPanel />\n    <EmbeddedGame />\n  </PlatformSettingsRuntime>\n);\n\nconst SettingsPanel = () => {\n  const { masterVolume, setMasterVolume } = usePlatformAudioSettings();\n  return (\n    <button onClick={() => setMasterVolume(masterVolume === 0 ? 1 : 0)}>\n      Toggle Master\n    </button>\n  );\n};\n\nconst EmbeddedGame = () => {\n  const settings = useInheritedPlatformSettings();\n  return <pre>{JSON.stringify(settings.audio, null, 2)}</pre>;\n};\n```\n\nRules:\n\n1. mount `PlatformSettingsRuntime persistence=\"local\"` once in the platform shell\n2. let `airjam.Host`, `airjam.Controller`, `AirJamHostRuntime`, and `AirJamControllerRuntime` supply the in-game settings boundary automatically\n3. embedded games inherit platform settings read-only\n4. keep platform settings limited to shared cross-game concerns like audio, accessibility, and feedback\n5. do not recreate feature-specific global settings stores alongside this runtime\n\n## Optional UI Primitives\n\n`@air-jam/sdk/ui` exports optional presentational primitives (`Button`, `Slider`, `PlayerAvatar`, `VolumeControls`).\nThese components are lifecycle-free: they do not create sockets or own host/controller session state.\n`VolumeControls` reads and writes the shared audio slice through the platform settings runtime.\n\n## Networked State (Host Source of Truth)\n\nUse `createAirJamStore` for shared game state synced from host to controllers.\n\n```tsx\nimport { createAirJamStore } from \"@air-jam/sdk\";\n\ninterface RuntimeState {\n  phase: \"lobby\" | \"playing\";\n  actions: {\n    setPhase: (\n      ctx: {\n        actorId: string;\n        role: \"controller\" | \"host\";\n        connectedPlayerIds: string[];\n      },\n      payload: { phase: \"lobby\" | \"playing\" },\n    ) => void;\n  };\n}\n\nexport const useGameStore = createAirJamStore<RuntimeState>((set) => ({\n  phase: \"lobby\",\n  actions: {\n    setPhase: (_ctx, { phase }) => set({ phase }),\n  },\n}));\n\nconst actions = useGameStore.useActions();\nactions.setPhase({ phase: \"playing\" });\n```\n\nUse `useGameStore.useActions()` for action dispatch. On controllers, action calls are proxied to the host automatically and actor identity is attached by the server.\n\nImportant identity rule:\n\n1. `ctx.actorId` is always the identity of the dispatcher\n2. if host code calls `useActions()`, then `ctx.actorId === \"host\"`\n3. if host code intentionally wants to run the same semantic action as controller `X`, use `useGameStore.asPlayer(\"X\")` instead of smuggling `controllerId` through an unrelated payload\n\nEvery action dispatch now resolves to an acknowledgement:\n\n1. `{ ok: true, status: \"accepted\", source, result? }`\n2. `{ ok: false, status: \"rejected\", source, reason, message, details? }`\n\nNetworked action contract:\n\n1. `() => Promise<ack>` for no-payload actions\n2. `(payloadObject) => Promise<ack>` for payload actions\n3. payload roots must be plain objects, not primitives, arrays, DOM events, or `T | undefined` unions\n4. if an action has no payload, omit it entirely instead of using `undefined` as part of an object-union payload type\n\nAction context includes `connectedPlayerIds`, so host actions can prune stale assignments without custom presence-sync actions.\n\nFor host-only local side effects such as sounds, local sim refs, or one-shot animations, use the store's host action listener seam instead of queueing ephemeral commands through replicated state:\n\n```tsx\nuseGameStore.useHostActionListener((event) => {\n  if (event.actionName !== \"fireProjectile\") {\n    return;\n  }\n\n  projectileRuntimeRef.current.spawn(event.payload);\n});\n```\n\nFor explicit host-side player impersonation, use the host-only player-action lane:\n\n```tsx\nconst playerActions = useGameStore.asPlayer(\"ctrl_2\");\n\nawait playerActions.joinTeam({ team: \"red\" });\n```\n\nWhen a store action should return an explicit semantic result or rejection, use\n`acceptAirJamAction(...)` and `rejectAirJamAction(...)`:\n\n```tsx\nimport {\n  acceptAirJamAction,\n  createAirJamStore,\n  rejectAirJamAction,\n} from \"@air-jam/sdk\";\n\nexport const useGameStore = createAirJamStore((set) => ({\n  aliveByPlayerId: {} as Record<string, boolean>,\n  actions: {\n    fire: ({ actorId }) =>\n      set((state) => {\n        if (!actorId || state.aliveByPlayerId[actorId] === false) {\n          return rejectAirJamAction(\"player_dead\", \"Dead players cannot fire.\");\n        }\n\n        return acceptAirJamAction({ cooldownMs: 4500 });\n      }),\n  },\n}));\n```\n\nIf an action returns `void`, Air Jam treats it as accepted automatically.\n\n## Runtime Pause Controls\n\nRuntime pause/play is independent from your game-owned match phase. Keep lobby,\nplaying, and ended state in your game store, and use explicit runtime commands\nonly for pause UI.\n\n```tsx\nconst host = useAirJamHost();\n\n<button\n  onClick={\n    host.runtimeState === \"playing\" ? host.pauseRuntime : host.resumeRuntime\n  }\n>\n  {host.runtimeState === \"playing\" ? \"Pause\" : \"Resume\"}\n</button>;\n```\n\n## One Correct Way (Default Path)\n\n1. Define one `airjam` app config with `createAirJamApp`.\n2. On controllers, publish input with `useControllerTick` + `useInputWriter`.\n3. On hosts, read input with `getInput` / `useGetInput`.\n4. For host gameplay loops, prefer `useHostTick(...)` over hand-rolled `requestAnimationFrame` or `setInterval` loops.\n5. Keep replicated gameplay state in `createAirJamStore`.\n6. Dispatch all store actions through `useActions()` zero-arg or payload-object calls and check the returned acknowledgement when outcome matters.\n\n## Canonical `airjam.config.ts`\n\nKeep one runtime/session declaration and use role wrappers directly in routes.\n\n```tsx\nimport { createAirJamApp, env } from \"@air-jam/sdk\";\nimport { gameInputSchema } from \"./types\";\n\nexport const airjam = createAirJamApp({\n  runtime: env.vite(import.meta.env),\n  controllerPath: \"/controller\",\n  // Optional agent-facing contracts belong here too.\n  // agent: agentContract,\n  input: {\n    schema: gameInputSchema,\n  },\n});\n```\n\n```tsx\nimport { Route, Routes } from \"react-router-dom\";\nimport { airjam } from \"./airjam.config\";\n\nexport const App = () => (\n  <Routes>\n    <Route\n      path=\"/\"\n      element={\n        <airjam.Host>\n          <HostView />\n        </airjam.Host>\n      }\n    />\n    <Route\n      path={airjam.paths.controller}\n      element={\n        <airjam.Controller>\n          <ControllerView />\n        </airjam.Controller>\n      }\n    />\n  </Routes>\n);\n```\n\nThis keeps runtime config, host input schema, route path ownership, and optional agent-facing contracts in one place.\n\nOptional future-facing game capability metadata should also live here, but the schema is intentionally experimental and lives in `@air-jam/sdk/capabilities`.\n\n## Environment Variables\n\n- `VITE_AIR_JAM_SERVER_URL` / `NEXT_PUBLIC_AIR_JAM_SERVER_URL`\n- `VITE_AIR_JAM_APP_ID` / `NEXT_PUBLIC_AIR_JAM_APP_ID`\n\n## Full Docs\n\n- Platform docs: https://airjam.io/docs\n- Monorepo docs index: `docs/docs-index.md`\n","readmeFilename":"README.md"}