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