diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index d14fbed1f2..bc293dd668 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -141,8 +141,8 @@ { "name": "arcade-canvas", "source": "plugins/arcade-canvas", - "description": "Play five retro Phaser mini-games in a Copilot canvas while agents work.", - "version": "1.0.2" + "description": "Play six retro Phaser mini-games in a Copilot canvas while agents work.", + "version": "1.1.0" }, { "name": "arch", diff --git a/docs/README.plugins.md b/docs/README.plugins.md index 31bdbb741e..fa32abc5d5 100644 --- a/docs/README.plugins.md +++ b/docs/README.plugins.md @@ -31,7 +31,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-plugins) for guidelines on how t | [acreadiness-cockpit](../plugins/acreadiness-cockpit/README.md) | Drive Microsoft AgentRC from Copilot chat: assess AI readiness, generate Copilot instructions (flat or nested with applyTo globs for monorepos), and manage policies. Produces a self-contained static HTML dashboard at reports/index.html. | 4 items | agentrc, ai-readiness, copilot-instructions, readiness-report, monorepo, policy, dashboard | | [ai-team-orchestration](../plugins/ai-team-orchestration/README.md) | Run a lightweight, role-separated AI development team with flexible tools, developer-selected models, proportionate planning, and optional QA. | 4 items | ai-team, multi-agent, sprint-planning, brainstorm, project-management, orchestration, developer-workflow | | [apng-studio](../plugins/apng-studio/README.md) | Interactive GitHub Copilot app canvas extension for building Animated PNG (APNG) files from frames. Draw or upload frames, tune per-frame timing and compositing, preview live, send the result to your phone by QR, and export an animated .png. | 1 items | animated-png, apng, copilot-canvas, frame-animation, image-export, interactive-canvas | -| [arcade-canvas](../plugins/arcade-canvas/README.md) | Play five retro Phaser mini-games in a Copilot canvas while agents work. | 1 items | arcade-games, copilot-canvas, interactive-canvas, phaser, retro-games, session-breaks | +| [arcade-canvas](../plugins/arcade-canvas/README.md) | Play six retro Phaser mini-games in a Copilot canvas while agents work. | 1 items | arcade-games, copilot-canvas, interactive-canvas, phaser, retro-games, session-breaks | | [arch](../plugins/arch/README.md) | Architecture and modernization toolkit: produce a cited architecture document for a locally-cloned repo, and generate a phased modernization plan that auto-runs Documentation mode when needed. | 1 items | architecture, modernization, documentation, migration, onboarding | | [arize-ax](../plugins/arize-ax/README.md) | Arize AX platform skills for LLM observability, evaluation, and optimization. Includes trace export, instrumentation, datasets, experiments, evaluators, AI provider integrations, annotations, prompt optimization, and deep linking to the Arize UI. | 9 items | arize, llm, observability, tracing, evaluation, instrumentation, datasets, experiments, prompt-optimization | | [automate-this](../plugins/automate-this/README.md) | Record your screen doing a manual process, drop the video on your Desktop, and let Copilot CLI analyze it frame-by-frame to build working automation scripts. Supports narrated recordings with audio transcription. | 1 items | automation, screen-recording, workflow, video-analysis, process-automation, scripting, productivity, copilot-cli | diff --git a/extensions/arcade-canvas/README.md b/extensions/arcade-canvas/README.md index 398ff9e2a0..67633afcbc 100644 --- a/extensions/arcade-canvas/README.md +++ b/extensions/arcade-canvas/README.md @@ -1,6 +1,6 @@ # Agent Arcade Canvas -A GitHub Copilot canvas that opens a retro arcade in the side panel. It serves the built Agent Arcade Phaser frontend and lets either the user or the agent switch between five mini-games. +A GitHub Copilot canvas that opens a retro arcade in the side panel. It serves the built Agent Arcade Phaser frontend and lets either the user or the agent switch between six mini-games. ## Games @@ -9,36 +9,24 @@ A GitHub Copilot canvas that opens a retro arcade in the side panel. It serves t - **Galaxy Blaster** — Galaga-style space shooter with formation enemies, attack patterns, and dual-shot power-up. - **Ninja Runner** — Classic platformer with double jumps, power-ups, warp pipes, and enemies. - **Planet Guardian** — Defender-style side-scrolling shooter with humanoid rescues and six enemy types. +- **Surface Defense** — Missile-defense strategy with six cities, three batteries, chain reactions, smart warheads, bombers, and hostile satellites. ## Files - `extension.mjs` — canvas declaration, loopback game server, static asset handling, and agent actions. - `game/` — compiled Phaser game frontend served inside the canvas. - `assets/` — game sprites, sounds, app icon, and `preview.png` for the extensions gallery. -- `package.json` — declares the Copilot SDK dependency and ESM entry point. - `copilot-extension.json` — Copilot extension name/version metadata. -- `canvas.json` — Awesome Copilot gallery metadata. ## Prerequisites -- **Node.js 20.19 or newer** because the Copilot SDK requires `node ^20.19.0 || >=22.12.0`. - The GitHub Copilot app canvas / UI-extensions experiment enabled. ## Install -Drop this folder at `~/.copilot/extensions/arcade-canvas/` for user scope, or in a repository at `.github/extensions/arcade-canvas/` for project scope. Then install dependencies from inside the copied folder: +Drop this folder at `~/.copilot/extensions/arcade-canvas/` for user scope, or in a repository at `.github/extensions/arcade-canvas/` for project scope. Do not install `@github/copilot-sdk` locally. Copilot provides it through the automatic extension module resolver. -```sh -# User scope -cd ~/.copilot/extensions/arcade-canvas - -# Or project scope, from the repository root -cd .github/extensions/arcade-canvas - -npm install -``` - -Reload extensions in the GitHub Copilot app, then open the `arcade-canvas` canvas. The canvas accepts an optional `defaultGame` input with one of these keys: `cosmic-rocks`, `alien-onslaught`, `galaxy-blaster`, `ninja-runner`, or `defender`. +Reload extensions in the GitHub Copilot app, then open the `arcade-canvas` canvas. The canvas accepts an optional `defaultGame` input with one of these keys: `cosmic-rocks`, `alien-onslaught`, `galaxy-blaster`, `ninja-runner`, `defender`, or `surface-defense`. ## Agent actions @@ -54,7 +42,16 @@ In the Agent Arcade repository, rebuild the committed canvas bundle after fronte npm run build:canvas ``` -That command builds the frontend, copies `dist/game` into `game/`, copies `dist/assets` into `assets/`, writes `assets/preview.png` for the Awesome Copilot gallery, and bundles `assets/canvas-background.webp` for the canvas-only space backdrop. +That command builds the frontend, copies `dist/game` into `game/`, copies the required assets into `assets/`, writes `assets/preview.png` for the Awesome Copilot gallery, and bundles `assets/canvas-background.webp` for the canvas-only space backdrop. The canvas package uses compressed M4A copies of WAV effects and removes unused desktop assets so it stays within the 8 MiB project-extension trust limit. + +## Awesome Copilot sync + +The published copy lives in `github/awesome-copilot` under `extensions/arcade-canvas/`. After rebuilding this canonical copy: + +1. Replace the Awesome Copilot `extensions/arcade-canvas/` directory with this directory. +2. Update `plugins/arcade-canvas/plugin.json`, including its version and six-game description. +3. Update `plugins/arcade-canvas/README.md`. +4. Run `npm run plugin:validate`, `npm run build`, and `npm run website:data` in the Awesome Copilot repository. ## Credits @@ -63,6 +60,8 @@ That command builds the frontend, copies `dist/game` into `game/`, copies `dist/ - Galaga-style game mechanics: [WesleyEdwards/galaga](https://github.com/WesleyEdwards/galaga) by Wesley Edwards. - Asteroids-style game mechanics: [phaser3-typescript](https://github.com/digitsensitive/phaser3-typescript) by digitsensitive. - Defender-style game mechanics and sound effects: [OpenDefender](https://github.com/mkinney/Opendefender) by mkinney. +- Missile-defense gameplay reference: [Basic Missile Command HTML and JavaScript Game](https://gist.github.com/straker/afc4e2a30b6df772a5f9f6ef01751d41) by straker (CC0 1.0). +- Surface Defense uses original synthesized WAV effects generated by `scripts/generate-surface-defense-audio.mjs`; no commercial-game audio is redistributed. - Retro game sound effects: ["Retro game sound effects"](https://opengameart.org/content/retro-game-sound-effects) by Vircon32 (Carra), published at OpenGameArt under [CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/). - Thanks to [John Papa](https://github.com/johnpapa) for his Alien Onslaught game PR. - Thanks to [Shayne Boyer](https://github.com/spboyer) for the initial PR to get Agent Arcade running in the GitHub App canvas. diff --git a/extensions/arcade-canvas/assets/asset-.md b/extensions/arcade-canvas/assets/asset-.md index 88354f6ff1..4058e6ac59 100644 --- a/extensions/arcade-canvas/assets/asset-.md +++ b/extensions/arcade-canvas/assets/asset-.md @@ -3,4 +3,5 @@ https://opengameart.org/content/2d-nature-platformer-tileset-16x16 https://opengameart.org/content/retro-game-sound-effects https://github.com/digitsensitive/phaser3-typescript https://github.com/WesleyEdwards/galaga -https://juhosprite.itch.io/simple-platformer-16 \ No newline at end of file +https://juhosprite.itch.io/simple-platformer-16 +https://gist.github.com/straker/afc4e2a30b6df772a5f9f6ef01751d41 \ No newline at end of file diff --git a/extensions/arcade-canvas/assets/defender/planet-guard-sprites.png b/extensions/arcade-canvas/assets/defender/planet-guard-sprites.png deleted file mode 100644 index 8656e3e5d2..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/planet-guard-sprites.png and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.m4a new file mode 100644 index 0000000000..895df64abb Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.ogg deleted file mode 100644 index 3e3f167728..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_baiterwarning.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.m4a new file mode 100644 index 0000000000..58b19d45c8 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.ogg deleted file mode 100644 index 2e7766e2dd..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_bonus.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.m4a new file mode 100644 index 0000000000..07172f7f0d Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.ogg deleted file mode 100644 index 48f06d10f3..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_enemydead.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.m4a new file mode 100644 index 0000000000..f7bc2b2c6a Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.ogg deleted file mode 100644 index 6ba909aa5e..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.m4a new file mode 100644 index 0000000000..736ef3d1a7 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.ogg deleted file mode 100644 index b1cd5f522d..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_enemyshoot2.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_explode.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_explode.m4a new file mode 100644 index 0000000000..a0e3522740 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_explode.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_explode.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_explode.ogg deleted file mode 100644 index 004f63f5c2..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_explode.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.m4a new file mode 100644 index 0000000000..95d4feee7d Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.ogg deleted file mode 100644 index c21e4be376..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_humanoiddead.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_laser.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_laser.m4a new file mode 100644 index 0000000000..af4e81328d Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_laser.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_laser.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_laser.ogg deleted file mode 100644 index 7c88c7b040..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_laser.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.m4a new file mode 100644 index 0000000000..76915cf0a7 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.ogg deleted file mode 100644 index 11d4015e6a..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_player1up.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.m4a new file mode 100644 index 0000000000..20f2c7bd3d Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.ogg deleted file mode 100644 index 39e8f0a0a5..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_playerdead.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_start.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_start.m4a new file mode 100644 index 0000000000..5187876421 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_start.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_start.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_start.ogg deleted file mode 100644 index 7b6a4c4701..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_start.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.m4a new file mode 100644 index 0000000000..8d68ba2b2f Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.ogg deleted file mode 100644 index 92e18e0dd7..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_thurst.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_warning.m4a b/extensions/arcade-canvas/assets/defender/sounds/sound_warning.m4a new file mode 100644 index 0000000000..4dcfe8cee7 Binary files /dev/null and b/extensions/arcade-canvas/assets/defender/sounds/sound_warning.m4a differ diff --git a/extensions/arcade-canvas/assets/defender/sounds/sound_warning.ogg b/extensions/arcade-canvas/assets/defender/sounds/sound_warning.ogg deleted file mode 100644 index 96a5629097..0000000000 Binary files a/extensions/arcade-canvas/assets/defender/sounds/sound_warning.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_laser2.ogg b/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_laser2.ogg deleted file mode 100644 index 6a2d4c5a7a..0000000000 Binary files a/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_laser2.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_zap.ogg b/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_zap.ogg deleted file mode 100644 index 3f6250d326..0000000000 Binary files a/extensions/arcade-canvas/assets/galaxy-blaster/sounds/sfx_zap.ogg and /dev/null differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2-black.png b/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2-black.png deleted file mode 100644 index daad6d14e7..0000000000 Binary files a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2-black.png and /dev/null differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2.png b/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2.png index 1dc5186804..90636b8bbe 100644 Binary files a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2.png and b/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet-2.png differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.png b/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.png deleted file mode 100644 index 8c58b86c2d..0000000000 Binary files a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.png and /dev/null differ diff --git a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.xml b/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.xml deleted file mode 100644 index 71e1ccf17a..0000000000 --- a/extensions/arcade-canvas/assets/galaxy-blaster/space_sheet.xml +++ /dev/null @@ -1,296 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/extensions/arcade-canvas/assets/preview.png b/extensions/arcade-canvas/assets/preview.png index 65d9bd8bd9..feb5404b29 100644 Binary files a/extensions/arcade-canvas/assets/preview.png and b/extensions/arcade-canvas/assets/preview.png differ diff --git a/extensions/arcade-canvas/assets/sounds/agent-arcade-voice.mp3 b/extensions/arcade-canvas/assets/sounds/agent-arcade-voice.mp3 deleted file mode 100644 index 9d6881b9ef..0000000000 Binary files a/extensions/arcade-canvas/assets/sounds/agent-arcade-voice.mp3 and /dev/null differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/airburst.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/airburst.m4a new file mode 100644 index 0000000000..60488d6f24 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/airburst.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/aircraft-alert.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/aircraft-alert.m4a new file mode 100644 index 0000000000..1ba8a61c51 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/aircraft-alert.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/bonus-city.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/bonus-city.m4a new file mode 100644 index 0000000000..925005ce40 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/bonus-city.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/game-over.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/game-over.m4a new file mode 100644 index 0000000000..16786cc96e Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/game-over.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/ground-impact.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/ground-impact.m4a new file mode 100644 index 0000000000..90bc9224fc Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/ground-impact.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/launch.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/launch.m4a new file mode 100644 index 0000000000..d0c2516899 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/launch.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/no-ammo.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/no-ammo.m4a new file mode 100644 index 0000000000..6588ab43a7 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/no-ammo.m4a differ diff --git a/extensions/arcade-canvas/assets/surface-defense/sounds/wave-start.m4a b/extensions/arcade-canvas/assets/surface-defense/sounds/wave-start.m4a new file mode 100644 index 0000000000..7341a71626 Binary files /dev/null and b/extensions/arcade-canvas/assets/surface-defense/sounds/wave-start.m4a differ diff --git a/extensions/arcade-canvas/extension.mjs b/extensions/arcade-canvas/extension.mjs index c8c56b0c10..f40f29ed5b 100644 --- a/extensions/arcade-canvas/extension.mjs +++ b/extensions/arcade-canvas/extension.mjs @@ -10,9 +10,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const gameRoot = path.join(__dirname, "game"); const assetsRoot = path.join(__dirname, "assets"); const indexPath = path.join(gameRoot, "index.html"); -const gameJsPath = path.join(gameRoot, "game.js"); -const alienOnslaughtJsPath = path.join(gameRoot, "scenes", "AlienOnslaught.js"); -const galaxyBlasterJsPath = path.join(gameRoot, "scenes", "GalaxyBlaster.js"); const games = [ { key: "cosmic-rocks", label: "Cosmic Rocks", icon: "☄️" }, @@ -20,11 +17,12 @@ const games = [ { key: "galaxy-blaster", label: "Galaxy Blaster", icon: "🚀" }, { key: "ninja-runner", label: "Ninja Runner", icon: "🥷" }, { key: "defender", label: "Planet Guardian", icon: "🛡️" }, + { key: "surface-defense", label: "Surface Defense", icon: "🎯" }, ]; const gameKeys = new Set(games.map((game) => game.key)); const defaultGame = "ninja-runner"; -const canvasBackgroundGames = ["cosmic-rocks", "alien-onslaught", "galaxy-blaster", "defender"]; +const canvasBackgroundGames = ["cosmic-rocks", "alien-onslaught", "galaxy-blaster", "defender", "surface-defense"]; const servers = new Map(); function normalizeGameKey(value) { @@ -96,6 +94,7 @@ async function renderIndex(entry) { const html = await readFile(indexPath, "utf8"); const bootstrap = `', `${bootstrap}\n `); } -async function renderGameJs() { - const js = await readFile(gameJsPath, "utf8"); - return js - .replaceAll("newW > 800 && newH > 400", "newW > 320 && newH > 220") - .replaceAll("game && newH > 400", "game && newH > 220") - .replaceAll("window.innerWidth > 800 && window.innerHeight > 400", "window.innerWidth > 320 && window.innerHeight > 220"); -} - -async function renderAlienOnslaughtJs() { - const js = await readFile(alienOnslaughtJsPath, "utf8"); - const layoutH = "Math.min(H, W * 3 / 4)"; - const layoutY = `((H - ${layoutH}) / 2)`; - return js - .replace("this.playerY = H * 0.92;", `this.playerY = ${layoutY} + ${layoutH} * 0.95;`) - .replace("this.alienGridY = Math.max(H * 0.20, 120);", `this.alienGridY = Math.max(${layoutY} + ${layoutH} * 0.10, 80);`) - .replace("const targetShieldH = H * 0.055;", `const targetShieldH = ${layoutH} * 0.065;`) - .replace("SCALE = Math.min(W / 1920, H / 1080);", "SCALE = Math.max(1.25, Math.min(W / 1920, H / 1080));") - .replace("this.alienCellW = Math.round(W * 0.055);", "this.alienCellW = Math.round(W * 0.068);"); -} - -async function renderGalaxyBlasterJs() { - const js = await readFile(galaxyBlasterJsPath, "utf8"); - return js - .replaceAll("SCALE = Math.min(CONV_X, CONV_Y);", "SCALE = Math.max(1.7, Math.min(CONV_X, CONV_Y));") - .replaceAll("OPPONENT_SIZE = Math.min(32 * SCALE, W / 35);", "OPPONENT_SIZE = Math.max(54, Math.min(32 * SCALE, W / 24));"); -} - async function streamFile(res, filePath) { const fileStat = await stat(filePath).catch(() => undefined); if (!fileStat?.isFile()) { @@ -324,22 +303,25 @@ async function streamFile(res, filePath) { async function handleSelectGame(entry, req, res) { let body = ""; req.setEncoding("utf8"); - req.on("data", (chunk) => { + for await (const chunk of req) { body += chunk; - }); - req.on("end", () => { - let input; - try { - input = JSON.parse(body || "{}"); - } catch { - res.writeHead(400, { "content-type": "text/plain; charset=utf-8" }); - res.end("Invalid JSON request body"); - return; - } - entry.selectedGame = normalizeGameKey(input.gameKey); - broadcast(entry, "selectGame", { gameKey: entry.selectedGame }); - sendJson(res, { selectedGame: entry.selectedGame }); - }); + } + let input; + try { + input = JSON.parse(body); + } catch { + res.writeHead(400, { "content-type": "text/plain; charset=utf-8" }); + res.end("Invalid JSON request body"); + return; + } + if (!input || typeof input !== "object" || Array.isArray(input) || !gameKeys.has(input.gameKey)) { + res.writeHead(400, { "content-type": "text/plain; charset=utf-8" }); + res.end("Expected a valid gameKey"); + return; + } + entry.selectedGame = input.gameKey; + broadcast(entry, "selectGame", { gameKey: entry.selectedGame }); + sendJson(res, { selectedGame: entry.selectedGame }); } async function handleRequest(entry, req, res) { @@ -382,33 +364,6 @@ async function handleRequest(entry, req, res) { return; } - if (url.pathname === "/game.js" || url.pathname === "/game/game.js") { - res.writeHead(200, { - "content-type": "text/javascript; charset=utf-8", - "cache-control": "no-cache", - }); - res.end(await renderGameJs()); - return; - } - - if (url.pathname === "/scenes/AlienOnslaught.js" || url.pathname === "/game/scenes/AlienOnslaught.js") { - res.writeHead(200, { - "content-type": "text/javascript; charset=utf-8", - "cache-control": "no-cache", - }); - res.end(await renderAlienOnslaughtJs()); - return; - } - - if (url.pathname === "/scenes/GalaxyBlaster.js" || url.pathname === "/game/scenes/GalaxyBlaster.js") { - res.writeHead(200, { - "content-type": "text/javascript; charset=utf-8", - "cache-control": "no-cache", - }); - res.end(await renderGalaxyBlasterJs()); - return; - } - const staticPath = url.pathname.startsWith("/assets/") ? resolveUnder(assetsRoot, url.pathname.slice("/assets".length)) : resolveUnder(gameRoot, url.pathname.startsWith("/game/") ? url.pathname.slice("/game".length) : url.pathname); @@ -459,7 +414,7 @@ await joinSession({ createCanvas({ id: "arcade-canvas", displayName: "Agent Arcade", - description: "A retro arcade canvas with five mini-games for waiting while agents work.", + description: "A retro arcade canvas with six mini-games for waiting while agents work.", inputSchema: { type: "object", properties: { @@ -524,6 +479,7 @@ await joinSession({ entry = await startServer(ctx.instanceId, normalizeGameKey(ctx.input?.defaultGame)); } else if (ctx.input?.defaultGame) { entry.selectedGame = normalizeGameKey(ctx.input.defaultGame); + broadcast(entry, "selectGame", { gameKey: entry.selectedGame }); } return { title: "Agent Arcade", diff --git a/extensions/arcade-canvas/game/game.js b/extensions/arcade-canvas/game/game.js index 7ed254bf64..b54a6bf3ed 100644 --- a/extensions/arcade-canvas/game/game.js +++ b/extensions/arcade-canvas/game/game.js @@ -6,14 +6,20 @@ import { GalaxyBlasterScene } from './scenes/GalaxyBlaster.js'; import { CosmicRocksScene } from './scenes/CosmicRocks.js'; import { AlienOnslaughtScene } from './scenes/AlienOnslaught.js'; import { PlanetGuardianScene } from './scenes/PlanetGuardian.js'; +import { SurfaceDefenseScene } from './scenes/SurfaceDefense.js'; +import { runtimeLayout } from './layout.js'; // Registry of available games +// pointerGame: the game aims with the mouse, so click-through must stay off +// while it is active (see __agentArcadeSetPointerGameActive in hud.js). const GAMES = [ { key: 'cosmic-rocks', scene: CosmicRocksScene, label: '☄️ Cosmic Rocks' }, { key: 'alien-onslaught', scene: AlienOnslaughtScene, label: '👾 Alien Onslaught' }, { key: 'galaxy-blaster', scene: GalaxyBlasterScene, label: '🚀 Galaxy Blaster' }, { key: 'ninja-runner', scene: NinjaRunnerScene, label: '🥷 Ninja Runner' }, { key: 'defender', scene: PlanetGuardianScene, label: '🛡️ Planet Guardian' }, + { key: 'surface-defense', scene: SurfaceDefenseScene, label: '🎯 Surface Defense', pointerGame: true }, ]; +const isPointerGame = (key) => !!GAMES.find(g => g.key === key)?.pointerGame; let currentGameKey; try { // Migrate localStorage from old "galaxy-shooter" name @@ -39,6 +45,8 @@ if (!GAMES.find(g => g.key === currentGameKey)) let game = null; function initGame() { refreshDimensions(); + const selectedGame = GAMES.find(g => g.key === currentGameKey); + const sceneOrder = [selectedGame, ...GAMES.filter(g => g !== selectedGame)]; game = new Phaser.Game({ type: Phaser.AUTO, parent: 'game', @@ -46,7 +54,7 @@ function initGame() { height: H, transparent: true, backgroundColor: 'rgba(0,0,0,0)', - scene: GAMES.map(g => g.scene), + scene: sceneOrder.map(g => g.scene), physics: { default: 'arcade', arcade: { gravity: { y: 1800 }, debug: false }, @@ -56,14 +64,28 @@ function initGame() { }); // Expose game instance for Playwright testing (no production impact) window.__phaserGame = game; - // Start the saved game (stop the default first scene if it's different) - if (currentGameKey !== GAMES[0].key) { - game.events.once('ready', () => { - game.scene.stop(GAMES[0].key); - game.scene.start(currentGameKey); - }); - } + guardMissingAudio(game); setupGameSwitcher(); + if (isPointerGame(currentGameKey)) { + window.__agentArcadeSetPointerGameActive?.(true); + } +} +/** + * Audio that fails to decode (missing codecs — e.g. WebKitGTK without the + * GStreamer plugin packages) never reaches the cache, and Phaser then throws + * "Audio key not found in cache" from sound.play()/add(). An exception inside + * the frame callback stops requestAnimationFrame for good and freezes the + * game, so hand back a silent sound for missing keys instead. + */ +function guardMissingAudio(game) { + const sm = game.sound; + const cache = game.cache?.audio; + if (!sm || !cache) + return; + const origAdd = sm.add.bind(sm); + sm.add = (key, config) => cache.exists(key) ? origAdd(key, config) : new Phaser.Sound.NoAudioSound(sm, key, config); + const origPlay = sm.play.bind(sm); + sm.play = (key, extra) => (cache.exists(key) ? origPlay(key, extra) : false); } function setupGameSwitcher() { // Expose game switcher for the HUD dropdown @@ -106,9 +128,12 @@ function setupGameSwitcher() { // click-through kills mousemove events, leaving no way to detect HUD exit). if (ab && ab.setClickThrough) ab.setClickThrough(false); + window.__agentArcadeSetPointerGameActive?.(isPointerGame(key)); const sel = document.getElementById('game-select'); - if (sel) + if (sel) { + sel.value = key; sel.blur(); + } game.canvas.focus(); }; } @@ -125,20 +150,24 @@ window.addEventListener('resize', () => { resizeDebounce = window.setTimeout(() => { const newW = window.innerWidth; const newH = window.innerHeight; - if (!game && newW > 800 && newH > 400) { + if (!game && newW > runtimeLayout.minimumWidth && newH > runtimeLayout.minimumHeight) { // First time: window is now full-screen — create the game refreshDimensions(); initGame(); } - else if (game && newH > 400) { + else if (game && newH > runtimeLayout.minimumHeight) { // Full-screen resize (could be unpause expand or genuine resize). // Update dimensions and resize the canvas, but never restart the // scene — the resume system handles unpause, and a simple resize // is enough for monitor/display changes. + const previousW = W; + const previousH = H; refreshDimensions(); - game.scale.resize(W, H); + if (W !== previousW || H !== previousH) { + game.scale.resize(W, H); + } } - // If newH <= 400 (pause shrink to HUD), skip entirely — + // Below the profile's minimum height (pause shrink to HUD), skip entirely — // keep W/H at full-screen values so the paused game state stays valid. }, 150); }); @@ -170,7 +199,7 @@ else { // If the window is already full-screen (e.g. Playwright tests or fast Tauri // init), create the game immediately since no resize event will fire. setTimeout(() => { - if (!game && window.innerWidth > 800 && window.innerHeight > 400) { + if (!game && window.innerWidth > runtimeLayout.minimumWidth && window.innerHeight > runtimeLayout.minimumHeight) { refreshDimensions(); initGame(); } diff --git a/extensions/arcade-canvas/game/hud.js b/extensions/arcade-canvas/game/hud.js index 5984485bf4..1cbc43f2e7 100644 --- a/extensions/arcade-canvas/game/hud.js +++ b/extensions/arcade-canvas/game/hud.js @@ -15,7 +15,16 @@ if ('mediaSession' in navigator) { }); } -// ── Tauri compatibility bridge ── +// ── Platform flag ── +// Linux/Wayland needs a few HUD differences (in-page Escape, pause button, +// self-drawn game