SLOPSHOPPER

arcade

The Claude Code Arcade: pixel games above the prompt, played by your work (an invading octopus, a duck hunt, Bug Command, which you can play along with, Dario…

newpanebandguardcommandtoast
★ 5v0.10.2MITupdated 2026-10-08mutlumehmet/claude-plugins/plugins/arcade
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · arcade
│ ┃ Arcade ✕ › fix the failing auth╭────────────────────────────────────────────╮ │ ┃ ● Octo Invader ★ a pixel octopus smashes a │ arcade │ │ ┃ Duck Hunt ☆ your commits shoot the duc ⏺ Read(src/auth.ts) │ Arcade: Octo Invader is your game. ▶ under │ │ ┃ Bug Command ☆ bugs fall on six cities; c ⎿ Read 6 lines │ it tries the next one, ☰ lists all 10 and │ │ ┃ Dario ☆ a side scroller: tool call ⏺ Update(src/auth.ts) │ sets the game new terminals start with (or │ │ ┃ Block Town ☆ your agents build a block ⎿ Added 2 lines, re╰────────────────────────────────────────────╯ │ ┃ Dragon Lair ☆ a pixel dragon that breath ⏺ Bash(bun test) ╭────────────────────────────────────────────╮ │ ┃ Jackpot ☆ a slot machine: every fini ⎿ 3 pass, 1 fail │ arcade │ │ ┃ Outlaw ☆ an Atari duel: you against │ 🐙 Push! The octopus pulls a plane out of │ │ ┃ Tama ☆ a Tamagotchi your work fee ● Done. refresh now re│ the sky │ │ ┃ Tetris ☆ Tetris where Claude's tool ╰────────────────────────────────────────────╯ │ ┃ ✻ Worked for 42s · done 4:20 PM │ ┃ ⟳ Check for an Arcade update │ ┃ A name plays it here. ☆ makes it the game › /arcade │ ┃ new terminals start with. Esc closes. ⎿ arcade: Arcade menu open: pick a game, ☆ makes it your default. │ │ ▶ next game ☰ all games and your default ◀ Octo Invader ▶ ☰ ⟳ ▄▄▄▄ Lv 1 ⌂ 0 ✈ 0 ⚒ 9 ▄██████████▄ ██████████████ ⟨Claude Code's own drawing⟩ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
▶ next game ☰ all games and your default ◀ Octo Invader ▶ ☰ ⟳ ▄▄▄▄ Lv 1 ⌂ 0 ✈ 0 ⚒ 9 ▄██████████▄ ██████████████ ⟨Claude Code's own drawing⟩
Pane · Arcade
● Octo Invader ★ a pixel octopus smashes a city while Claud Duck Hunt ☆ your commits shoot the ducks, failed tools Bug Command ☆ bugs fall on six cities; click the sky to Dario ☆ a side scroller: tool calls bring coins, b Block Town ☆ your agents build a block town, a castle o Dragon Lair ☆ a pixel dragon that breathes fire when you Jackpot ☆ a slot machine: every finished turn pulls Outlaw ☆ an Atari duel: you against the bugs Tama ☆ a Tamagotchi your work feeds Tetris ☆ Tetris where Claude's tools drop the piece ⟳ Check for an Arcade update A name plays it here. ☆ makes it the game new terminals start with. Esc closes.
README

arcade

The Claude Code Arcade: pixel games in the line above the prompt, played by your work. Every tool call, commit, merge and failed test moves the game on. Part of claude-plugins.

Contents: Quick start · The games · Switching games · Game guides · Moments · Tuning the moments · Scores · What it reads and does · Install · Known gaps

Octo Invader in a terminal: a pixel octopus smashes a city above the prompt while Claude edits, commits and merges

Quick start

Type these at the Claude Code prompt, then pick a scope (user is the usual one). There is nothing to set up.

/plugin marketplace add mutlumehmet/claude-plugins
/plugin install arcade@mehmetmutlu

Octo Invader starts by itself, and the first time a short notice says where the controls are. Then just work: the game plays along. Above the game sit small buttons:

ButtonWhat it does
◀ ▶The previous or next game, in this terminal
☰The game menu: every game with a line about it. A name plays it here, ☆ makes it your default
☆ make defaultShows once this terminal plays a game other than your default; makes it the game every new terminal starts with
⟳Checks for a new Arcade version and installs it (the same as /plugin update arcade@mehmetmutlu), then puts /reload-plugins in the prompt for you. When a newer version is out it lights up as a yellow ● ⟳ update 0.x.y

Updates: Claude Code does not update plugins from this marketplace on its own unless you turn it on (/plugin, Marketplaces, mehmetmutlu, Enable auto-update). Without that, press ⟳ now and then, or type /arcade update. So you hear about it, each new terminal looks once for a newer version (it reads the version number in this repository's plugin.json on GitHub, nothing else). When one is out, ⟳ turns into a yellow ● ⟳ update 0.x.y and a notice says so, once per version. It never installs anything until you press it, and never checks again while the terminal is open. /arcade update check off stops the look, on brings it back.

The games

<table> <tr><td> <b>Octo Invader</b> <code>/arcade octopus</code>: a pixel octopus smashes a city the full width of the line while Claude edits<br> <img src="../../docs/images/octo-invader.gif" width="800" alt="Octo Invader strip"> </td></tr> <tr><td> <b>Duck Hunt</b> <code>/arcade duck</code>: your moments shoot the ducks down, failed tools let them fly away<br> <img src="../../docs/images/duck-hunt.gif" width="800" alt="Duck Hunt strip"> </td></tr> <tr><td> <b>Bug Command</b> <code>/arcade bugs</code>: bugs fall on six cities; Claude's tools shoot them down, and you can click the sky to fire too<br> <img src="../../docs/images/bug-command.gif" width="800" alt="Bug Command strip"> </td></tr> <tr><td> <b>Dario</b> <code>/arcade dario</code>: a side scroller; tool calls bring ? blocks and coins, a failed tool sends a bug<br> <img src="../../docs/images/dario.gif" width="800" alt="Dario strip"> </td></tr> <tr><td> <b>Block Town</b> <code>/arcade town</code>: your agents build a medieval town in blocks; big moments raise a castle<br> <img src="../../docs/images/block-town.gif" width="800" alt="Block Town strip"> </td></tr> <tr><td> <img src="../../docs/images/arcade-hero.gif" width="400" align="right" alt="The five small games: dragon, slot machine, duel, Tamagotchi, Tetris"> Five small games at the right of the line:<br> <b>Dragon Lair</b> <code>/arcade dragon</code>: a pixel dragon that breathes fire when you ship<br> <b>Jackpot</b> <code>/arcade jackpot</code>: a slot machine; every finished turn pulls the lever<br> <b>Outlaw</b> <code>/arcade outlaw</code>: an Atari duel; you fire at the good moments, the bug when a tool fails<br> <b>Tama</b> <code>/arcade tama</code>: a Tamagotchi your work feeds, or it packs its bags<br> <b>Tetris</b> <code>/arcade tetris</code>: Claude's tools drop the pieces </td></tr> </table>

Switching games

The buttons above do the everyday switching. Everything else is a word after /arcade, and each Claude Code config directory (an account) keeps its own choice.

CommandWhat it does
/arcadeOpens the game menu (in claude -p, lists the games instead)
/arcade duckPlays Duck Hunt in this terminal; other terminals and your default stay as they are
/arcade default duckMakes Duck Hunt the game every new terminal starts with, and plays it here
/arcade nextThe next game in this terminal
/arcade random, rotate, all, offHow new terminals pick: one at random, the next one in turn, every game at once, or none. /arcade default <game> goes back to one fixed game
/arcade pool dragon tetrisPicks only from these games (for random, rotate and all)
/arcade hideClears this terminal; /arcade next brings a game back
/arcade updateChecks for a new Arcade version and installs it, like ⟳
/arcade update check offStops the look for a new version when a terminal opens (on brings it back)
/arcade helpThe setting, which games this terminal shows, and these commands

A game that is not shown keeps playing and keeps its score for this terminal; it only stops drawing and stays quiet. A game's own command (/dragon, /duck and the rest) shows its score and plays its practice moves.

Game guides

What each game does with your work, its score line and its practice moves.

Octo Invader (octopus)

A pixel octopus invades a city that runs the full width of the line above the prompt. It acts out what Claude is doing, Godzilla style, and pulls planes out of the sky when something worth celebrating happens.

octo-invader in a terminal: Claude reads, edits, commits and merges while the octopus walks the streets, smashes a building, pulls a plane out of the sky and takes the city

The strip on its own, as the mod draws it:

The octopus strip: a city the width of the terminal, buildings toppled, a plane downed, a flag on the rubble

Both GIFs are drawn by the mod's own code from a scripted session (a read, an edit, a web search, a commit, a merge); the window around the strip is a mock up of a terminal.

  • While Claude works the octopus shows it: it hops when you send a message, hovers over the rooftops while Claude thinks, walks the streets on its tentacles while a file is read, hunts about with a ? while Claude searches, and takes to the sky for web and MCP tools.
  • Edits and shell commands are demolition: it walks up to the nearest building and pounds it down a storey at a time until it falls with a CRASH!. Fallen buildings rise again after about 45 seconds, so the city never runs out.
  • Each subagent is a baby octopus that swims behind it in a line, paddles faster as the subagent works, and swims home when it finishes (or sinks when it fails).
  • A failed tool leaves it dazed with stars over its head; two minutes of quiet and it curls up asleep.
  • Moments: a small one is a squirt of ink, a medium one a plane snatched out of the sky and thrown down (BOOM!), a big one a RAMPAGE! through the streets, and a merge, release, deploy, streak or record ends with a flag on the rubble and THE CITY IS MINE.
  • The score sits at the right end of the strip: Lv 3 ⌂ 12 ✈ 4 ⚒ 140 (level, buildings toppled, planes downed, tool calls).
  • /octopus shows the score; /octopus ink, plane, rampage and conquer show off each size.

Duck Hunt (duck)

A marsh the full width of the line above the prompt, with the grass along the bottom, a tree, a dog and ducks. Your work does the shooting.

Duck Hunt in a terminal: a test fails and the dog laughs, the tests go green for a double, a commit shoots a duck, a merge is a perfect round

The strip on its own, as the mod draws it:

The Duck Hunt strip: a marsh the width of the terminal, the dog in the grass, ducks flushed and shot down

Both GIFs are drawn by the Arcade's own code from a scripted session (a read, a failing test, an edit, passing tests, a commit, a merge); the window around the strip is a mock up of a terminal.

  • While Claude works the dog sniffs along the grass, faster while a tool runs, and now and then flushes a duck that flaps across the sky.
  • Moments: a small one is a shot (a flash of the crosshair), a medium one shoots a duck down and the dog pops up from the grass holding it, a big one is a double (DOUBLE!, the dog holds two), and a merge, release, deploy, streak or record is a PERFECT! round with feathers everywhere.
  • A failed tool lets the duck in the air get away (FLY AWAY), and the dog comes up laughing. Two minutes of quiet and the dog lies down asleep.
  • Rounds: every ten ducks down is a new round (ROUND 3).
  • The score sits at the right end of the strip: R 2 ▼ 14 ↗ 3 ⚒ 140 (round, ducks down, ducks that got away, tool calls).
  • /duck shows the score; /duck shot, hunt, double, perfect and flyaway are practice that counts nothing.

Bug Command (bugs)

Six cities and three silos along the bottom of the line above the prompt, the full width of it. Bugs fall on the cities; Claude's work fires the counter missiles, and you can fire too. The one Arcade game you can play along with while you wait.

Bug Command in a terminal: a failed test drops a fast bug and you click the sky to shoot it down, a bug gets through and a city falls, the tests going green fire a salvo that rebuilds it, and a commit is a sure hit

The strip on its own, as the mod draws it:

The Bug Command strip: six cities and three silos the width of the terminal, red bug trails, blue counter missiles, blasts, and the mouse pointer firing

Both GIFs are drawn by the Arcade's own code from a scripted session (a read, a failing test, an edit, passing tests, a commit), with the mouse clicks played into the sky's own pointer handler; the window around the strip is a mock up of a terminal.

  • While Claude works a bug falls now and then, a red trail from the top towards a city. A failed tool drops a fast one (INCOMING).
  • Every finished tool call fires a shot from the nearest silo at the lowest bug; most of them hit. A blast takes out every bug inside it, and each bug it takes out blasts too.
  • Moments: a small one is a shot (or a flare in an empty sky), a medium one a sure hit, a big one a salvo at every bug in the sky that also rebuilds a fallen city (BONUS CITY).
  • A bug that gets through ruins its city. When every city has fallen it is THE END, and new cities go up.
  • You can shoot: click anywhere in the sky and the nearest silo fires there. After a click the sky has the keyboard: the arrows move the crosshair (with shift, faster), space or Enter fires at it, and 1, 2, 3 fire from the left, middle or right silo. A silo you fire from greys out for a moment. Esc hands the keyboard back to the prompt.
  • The score sits at the right end of the strip: ✸ 14 ☞ 5 ✝ 2 ⚒ 140 ⌂ 6 (bugs shot down, the ones you shot yourself, cities lost, tool calls, cities standing).
  • /bugs shows the score and the controls; /bugs flare, shot and salvo are practice that counts nothing, and /bugs incoming drops a practice bug to shoot at.

Dario (dario)

A side scrolling course the full width of the line above the prompt, with a ground of bricks, clouds, bushes and pipes. Dario runs while Claude works; you only watch.

Dario in a terminal: tool calls bring ? blocks and coins, a failing test sends a bug that knocks into Dario, green tests are a course clear at the flag pole, a commit stomps a bug and a merge is a world clear with fireworks

The strip on its own, as the mod draws it:

The Dario strip: a brick ground the width of the terminal, Dario running, ? blocks, a bug, pipes and the flag pole

Both GIFs are drawn by the Arcade's own code from a scripted session (a read, a search, a failing test, an edit, passing tests, a commit, a merge); the window around the strip is a mock up of a terminal.

  • While Claude works the course scrolls by, fastest while a tool runs, and Dario jumps the pipes on its way. Two minutes of quiet and he sits down for a nap.
  • Every finished tool call brings a ? block: Dario jumps, bumps it and a coin flies out. With two blocks already waiting, the coin comes straight away.
  • A failed tool sends a bug walking in, and it knocks into Dario (OUCH).
  • Moments: a small one is a hop and a sparkle, a medium one a bug stomped flat, a big one a flag pole: Dario slides down it for a COURSE CLEAR! and the next course. A merge, release, deploy, streak or record is a WORLD CLEAR! with fireworks.
  • The score sits at the right end of the strip: 1-3 ◎ 34 ✪ 5 ✗ 2 ⚒ 140 (world and course, coins, bugs stomped, knocks, tool calls). Every hundred coins is a 1UP.
  • /dario shows the score; /dario coin, ouch, stomp, clear and world are practice that counts nothing.

Block Town (town)

A town in blocks, seen from the side, the full width of the line above the prompt: grass, dirt and stone, then houses, farms, a well, towers and a grove of trees. Your agents build it while they work, and it grows for as long as the terminal is open, so a glance in the middle of a long session shows how much got done.

Block Town in a terminal: the main agent and a helper villager build a house, a farm and a grove while sheep, a pig and chickens wander between them, a failing test brings a creeper, green tests raise part of the castle, a commit finishes a house and a merge builds the castle

The strip on its own, as the mod draws it:

The Block Town strip: grass, dirt and stone the width of the terminal, houses, a farm with a sheep and a pig, chickens, trees and a finished castle

Both GIFs are drawn by the Arcade's own code from a scripted session (edits and reads with a subagent helping, a failing test, edits, passing tests, a commit, a merge); the window around the strip is a mock up of a terminal.

  • Every tool call lays blocks on the building going up (two, an edit or a write three), from the bottom row up, with scaffolding at its corners. The main agent's villager does it; each subagent sends a helper villager of its own colour, who goes home once that subagent is quiet.
  • Animals live in the town as it earns them: a sheep and a pig for every finished farm, a chicken for every house, a cow for every hall, as many as the band has room for. They wander in front of the town and graze on open grass between the buildings, run from a creeper, and lie down when the town sleeps. They come from the buildings, so nothing about them is saved.
  • A failed tool brings a creeper that walks up to a building, flashes and blows a hole in it (BOOM); the villagers build it back before starting anything new.
  • Moments: a small one plants a tree (trees grow as the work goes on, sapling to full tree; once the grove is full, they grow faster instead), a medium one finishes the building going up (HOUSE BUILT), a big one raises a third of the castle at the right end of the band (THE CASTLE GROWS), and a merge, release, deploy, streak or record raises the rest at once (CASTLE BUILT!, fireworks).
  • When the band is full the oldest building is torn down and built again, a house as a two storey hall.
  • The score sits at the right end of the strip: Village ▦ 309 ⌂ 2 ♣ 5 ♜ 1 ⚒ 27 (the town's size from camp, hamlet, village and town to city, blocks laid, houses, trees, castles, tool calls). Two minutes of quiet and the villagers go indoors.
  • One town per terminal: every terminal builds its own town, starting from an empty field, and it keeps growing while the town is not shown.
  • /town shows the score; /town build, tree, finish, castle and creeper are practice that counts nothing. /town reset starts over: it asks first, and only /town reset yes within a minute clears this terminal's town and its score.

Dragon Lair (dragon)

A small one colour pixel dragon that lives at the right end of the line above the prompt. It acts out what Claude is doing and breathes fire when something worth celebrating happens.

  • While Claude works the dragon shows it: it hops when you send a message, raises its head while Claude thinks, bends over the page while a file is read, paces while a file is edited, beats its wings for shell commands and takes off for web and MCP tools.
  • Each subagent hatches from an egg as a small dragon that flies beside it, flaps faster as the subagent works, and flies home when it finishes (or falls when it fails).
  • A failed tool drops its head; two minutes of quiet and it falls asleep.
  • Moments: a small one is a puff of smoke, a medium one a breath of fire, a big one a blaze, and a merge, release, deploy, streak or record a roar.
  • Gold builds up with every moment while the terminal is open. Under the dragon: Lv 3 ◆ 55 ★ 9 ⚒ 58 (level, gold, wins, tool calls).
  • /dragon shows the hoard; /dragon puff, fire, blaze and roar show off each size.

Jackpot (jackpot)

A pixel slot machine at the right end of the line above the prompt. Every finished turn pulls the lever; the moments worth celebrating earn golden spins with better odds.

  • Every finished turn pulls the lever once. Reels blur, stop one by one and bounce as they lock.
  • Medium moments queue one golden spin, big moments three: better odds, double pay, never a loss.
  • Five clean turns in a row (no tool error) raise the multiplier by one, up to ×5; a failed tool resets it.
  • Payouts: three 7s pay 100 chips, three dragons 50, diamonds 25, bells 15, stars 10, cherries 8; a cherry pair 3, any other pair 2. A jackpot strobes the cabinet and spills coins.
  • Under the machine: ◉ 2967 ×1 ▲ 2 ✦ 0 ♛ 1 (chips, multiplier, clean streak, golden spins waiting, jackpots).
  • /jackpot shows the rules and stats; /jackpot spin, golden and demo are practice spins that pay nothing.

Outlaw (outlaw)

A one colour Atari Outlaw duel at the right end of the line above the prompt. Your gunslinger fires at the moments worth celebrating, the bug fires back when a tool fails.

  • Medium moments are one shot for your gunslinger, big moments three. A failed tool is a shot for the bug.
  • A hit is aimed at eye level and clears the cactus; a miss is from the hip and takes a chunk out of the cactus, which grows back between duels.
  • Between duels the two pace, shift their weight and tip their hats, a tumbleweed rolls by and a vulture circles; while Claude thinks they stand with a hand on the gun.
  • Under the duel: YOU 7 : 3 BUGS ▲ 4 ★ 6 (score, your run of hits, your best run).
  • /outlaw shows the score and rules; /outlaw draw is a practice duel.

Tama (tama)

A classic Tamagotchi in a small LCD at the right end of the line above the prompt. It lives in real time while the terminal is open, and your work is what feeds it. A new terminal starts with a new egg.

  • It hatches from an egg after three finished turns, then grows from baby to child to teen to adult over real days. The adult it becomes depends on how it was raised: hard working, a rascal, or well fed.
  • Hunger drops every 20 minutes and joy every hour, in real time. Every finished turn is a meal.
  • Medium moments cheer it up a little, big moments a lot (hearts float up).
  • A failed tool leaves a mess; five clean turns in a row tidy one away. Three messes, or an empty stomach, and it falls ill.
  • Left hungry for twelve hours it packs its bags and leaves an egg behind. It never dies.
  • At night (23:00 to 07:00 local time) it sleeps.
  • Under the LCD: ♨ ▮▮▮▯ ♥ ▮▮▯▯ ✧ ▮▮▮▮ (food, joy, clean).
  • /tama says how it is doing; /tama feed, play and clean are hand care, a few times a day.

Tetris (tetris)

A classic falling-piece Tetris in a small handheld screen at the right end of the line above the prompt. Claude's tools drop the pieces; nobody knows what comes next.

  • Every tool Claude runs drops a piece; a simple placement AI picks where it lands. The next piece is never shown.
  • A full row clears, with the classic scoring (40, 100, 300 or 1200 for one to four rows, times the level). Every ten rows is a level.
  • A failed tool pushes up a garbage row with one gap in it.
  • Medium moments clear one row from the bottom, big moments three.
  • When the stack reaches the lid the game ends, the well empties and a new game starts. The best score stays until the terminal closes.
  • Under the screen: ▤ 35 ◆ 4250 Lv 3 (rows, score, level).
  • /tetris shows the score and rules; /tetris drop adds pieces, /tetris clear clears a row.

Moments

The Arcade spots the moments once and hands each one to every game, coding or not.

SizeMoments
SmallA finished turn, a file saved, every 10th tool call in a turn
MediumA commit, a push, a pull request opened, tests passing, any skill run, a subagent finished, a plan approved, an MCP tool that sends, creates, posts or publishes, a long document written, praise in your message
BigA merge, a release (gh release create, npm publish), a production deploy (vercel --prod, netlify deploy --prod, fly deploy and others), tests back to green after failing, a deliverable made (PDF, DOCX, XLSX, PPTX, images), a published page, a finished task list of three or more, a marathon turn (10 minutes, 30 tools, no errors), three subagents done in one turn, a 3, 7, 30 or 100 day streak, the 100th, 1,000th or 10,000th tool call

A commit that says "nothing to commit" or a push that says "Everything up-to-date" counts for nothing. Tool calls inside subagents do not count; their finishing does.

Tuning the moments

All optional, and none of it is asked at install. /arcade moments shows what is set; /arcade moments <key> <value> sets one and /arcade moments <key> none clears it. Kept per Claude Code config directory.

KeyWhat it is forExample
big_skillsSkills whose run is a big moment (every other skill is medium)/arcade moments big_skills release-notes, publish-report
quiet_skillsSkills that celebrate nothing/arcade moments quiet_skills commit
big_commandsA regular expression of shell commands whose success is big/arcade moments big_commands make ship
medium_commandsA regular expression of shell commands whose success is medium/arcade moments medium_commands terraform apply
praise_wordsExtra words that count as praise, on top of the built-in list (thanks, great, perfect and a few in other languages)/arcade moments praise_words nice one, cheers

Upgrading from 0.9.0 or earlier: those versions kept the game choice and these keys as plugin settings (pluginConfigs in settings.json). The Arcade no longer declares them, so set them again with the commands above.

Scores

Every game starts from zero in each terminal: the score, Block Town's town and Tama's pet belong to that terminal and last until it closes. Two terminals play two separate games. Nothing is saved between sessions; a reload of the pl

Source 3 files
hooks/arcade.jsx 5176 lines
1// Built by scripts/build.sh from src/ with esbuild@0.25.10. Do not edit: edit src/ and rebuild.
2// src/arcade.tsx
3import { atom as atom12, read as read12, update as update11 } from "claude-code";
4
5// src/games/dragon-lair.tsx
6import { atom as atom2, read as read2, update } from "claude-code";
7
8// src/games/dragon-sprite.ts
9const DRAGON_WIDTH = 26;
10const PIXEL_ROWS = 10;
11const BASE = 2;
12const BODY = {
13  x: 7,
14  y: 4,
15  frames: {
16    still: ["..#########", "############", "..##########"],
17    breathe: ["..#########", ".###########", "..##########"]
18  }
19};
20const WING = {
21  x: 6,
22  y: -2,
23  frames: {
24    up: ["#.......", "##......", "######..", ".#.#.##.", "..#.#.##", "...#####"],
25    mid: ["........", "........", "#.......", ".######.", "..#.#.##", "...#####"],
26    down: ["........", "........", "........", "........", ".######.", "#.#.#.##"]
27  }
28};
29const HEAD = {
30  x: 16,
31  y: 0,
32  frames: {
33    look: ["...#.#....", "..######..", ".##.######", ".##...###.", "##........"],
34    blink: ["...#.#....", "..######..", ".#########", ".##...###.", "##........"],
35    up: ["...#.#....", "..##.###..", ".#########", ".##...###.", "##........"],
36    open: ["...#.#....", "..######..", ".##.######", ".##.......", "##....###."],
37    droop: ["..........", "...#.#....", "..######..", ".##.######", "##....###."],
38    doze: ["..........", "...#.#....", "..######..", ".#########", "##....###."]
39  }
40};
41const TAIL = {
42  x: 0,
43  y: 3,
44  frames: {
45    low: ["#........", "##.......", ".##......", "..#####.."],
46    high: ["##.......", "#........", "##.......", ".######.."],
47    flat: [".........", ".........", "###......", "..#####.."]
48  }
49};
50const LEGS = {
51  x: 8,
52  y: 7,
53  frames: {
54    stand: ["##.##..##.##"],
55    stepA: ["#..##...#..##"],
56    stepB: ["##..#...##..#"],
57    tuck: ["............"]
58  }
59};
60const MOUTH = { x: 26, y: BASE + 2 };
61const NOSTRIL = { x: 25, y: BASE + 2 };
62const EYE = { x: 19, y: BASE + 2 };
63function draw(ink, columns, pose2, dx = 0, dy = 0) {
64  const put5 = (part, frame10) => {
65    const rows = part.frames[frame10] ?? Object.values(part.frames)[0] ?? [];
66    rows.forEach((row, y) => {
67      for (let x = 0; x < row.length; x++) {
68        if (row[x] !== "#") continue;
69        const px = part.x + x + dx;
70        const py = BASE + part.y + y + dy;
71        if (px >= 0 && px < columns && py >= 0 && py < PIXEL_ROWS) ink[py * columns + px] = 1;
72      }
73    });
74  };
75  put5(TAIL, pose2.tail);
76  put5(BODY, pose2.body);
77  put5(LEGS, pose2.legs);
78  put5(WING, pose2.wing);
79  put5(HEAD, pose2.head);
80}
81const BABY = {
82  up: [".#....", "#.#...", ".###.#", "..####", "..#..."],
83  down: ["......", "....#.", ".####.", "#.###.", "..#..."]
84};
85function drawBaby(ink, columns, x, y, frame10) {
86  BABY[frame10].forEach((row, dy) => {
87    for (let dx = 0; dx < row.length; dx++) {
88      if (row[dx] !== "#") continue;
89      const px = Math.round(x) + dx;
90      const py = Math.round(y) + dy;
91      if (px >= 0 && px < columns && py >= 0 && py < PIXEL_ROWS) ink[py * columns + px] = 1;
92    }
93  });
94}
95
96// src/shown.ts
97import { atom, read } from "claude-code";
98const shown = atom({ plugin: "arcade", key: "shown" }, []);
99async function isShown($, id) {
100  return (await read($, shown)).includes(id);
101}
102
103// src/games/dragon-lair.tsx
104const ID = "dragon";
105const RASTER = "dragon";
106const ROWS = PIXEL_ROWS / 2;
107const COLUMNS = DRAGON_WIDTH + 9;
108const FPS_MS = 66;
109const INK = 16777216;
110const NONE = 16777216;
111const hoard = atom2({ plugin: "arcade", key: "dragonHoard" }, { gold: 0, meals: 0, feats: 0 });
112const mood = atom2({ plugin: "arcade", key: "dragonMood" }, "idle");
113const feat = atom2({ plugin: "arcade", key: "dragonFeat" }, "");
114async function notify($, text) {
115  if (!!await isShown($, ID)) $.ui.toast(text);
116}
117const SHOW_FRAMES = { puff: 20, breath: 32, blaze: 48, roar: 72 };
118function workOf(tool11) {
119  if (tool11 === "Read") return "read";
120  if (tool11 === "Grep" || tool11 === "Glob" || tool11 === "LSP") return "search";
121  if (tool11 === "Edit" || tool11 === "Write" || tool11 === "NotebookEdit") return "edit";
122  if (tool11 === "WebFetch" || tool11 === "WebSearch" || tool11.startsWith("mcp__")) return "web";
123  if (tool11 === "Agent" || tool11 === "Task") return "agent";
124  return "bash";
125}
126const SLOTS = [
127  { x: 28, y: 0 },
128  { x: 28, y: 5 },
129  { x: 0, y: 0 }
130];
131const HOME = { x: 18, y: 0 };
132const sim = {
133  t: 0,
134  lastActivity: 0,
135  isTurn: false,
136  working: [],
137  show: null,
138  sadUntil: -1,
139  alertUntil: -1,
140  levelUntil: -1,
141  level: 1,
142  particles: [],
143  dx: 0,
144  dy: 0,
145  requestId: null,
146  isBlitting: false,
147  mood: "idle",
148  babies: []
149};
150const rand = (a, b) => a + Math.random() * (b - a);
151const pick = (list2) => list2[Math.floor(Math.random() * list2.length)];
152const levelOf = (gold) => Math.floor(Math.sqrt(gold / 10)) + 1;
153const every = (n) => sim.t % n === 0;
154const cycle = (frames, n) => frames[Math.floor(sim.t / n) % frames.length];
155function activity() {
156  const { t } = sim;
157  if (sim.show && t < sim.show.until) return sim.show.kind;
158  if (t < sim.sadUntil) return "sad";
159  if (t < sim.alertUntil) return "alert";
160  const last2 = sim.working[sim.working.length - 1];
161  if (last2) return last2.work;
162  if (sim.isTurn) return "think";
163  if (t - sim.lastActivity > 120 * 1e3 / FPS_MS) return "sleep";
164  return "idle";
165}
166function moodOf(a) {
167  if (a === "puff" || a === "breath" || a === "blaze" || a === "roar") return "fire";
168  if (a === "sad" || a === "sleep" || a === "idle") return a;
169  return "work";
170}
171const statsLine = (stash) => `Lv ${levelOf(stash.gold)}  \u25C6 ${stash.gold}  \u2605 ${stash.feats}  \u2692 ${stash.meals}`;
172const centred = (text) => " ".repeat(Math.max(0, Math.floor((DRAGON_WIDTH - text.length) / 2))) + text;
173const showLeft = () => sim.show ? sim.show.until - sim.t : 0;
174function pose(a) {
175  const isBlink = sim.t % 55 < 2;
176  const fast = ["up", "mid", "down", "mid"];
177  switch (a) {
178    case "sleep":
179      return { body: cycle(["still", "breathe"], 20), wing: "down", head: "doze", tail: "flat", legs: "stand" };
180    case "sad":
181      return { body: "still", wing: "down", head: "droop", tail: "flat", legs: "stand" };
182    case "alert":
183      return { body: "still", wing: sim.alertUntil - sim.t > 8 ? "up" : "mid", head: "look", tail: "high", legs: lift(a) < 0 ? "tuck" : "stand" };
184    case "think":
185      return { body: cycle(["still", "breathe"], 12), wing: "down", head: isBlink ? "blink" : "up", tail: cycle(["low", "high"], 12), legs: "stand" };
186    case "read":
187      return { body: "still", wing: "down", head: isBlink ? "doze" : "droop", tail: "low", legs: "stand" };
188    case "search":
189      return { body: "still", wing: "mid", head: cycle(["look", "up"], 5), tail: cycle(["low", "high"], 5), legs: "stand" };
190    case "edit":
191      return { body: "still", wing: "down", head: "look", tail: cycle(["low", "high"], 3), legs: cycle(["stepA", "stand", "stepB", "stand"], 2) };
192    case "bash":
193      return { body: "still", wing: cycle(["up", "down"], 2), head: cycle(["look", "blink"], 6), tail: cycle(["low", "high"], 2), legs: "stand" };
194    case "web":
195      return { body: "still", wing: cycle(fast, 2), head: "look", tail: "flat", legs: "tuck" };
196    case "agent":
197      return { body: "still", wing: "mid", head: "droop", tail: "low", legs: "stand" };
198    case "idle": {
199      const isStretch = sim.t % 120 < 14;
200      return {
201        body: cycle(["still", "breathe"], 25),
202        wing: isStretch ? cycle(["mid", "up"], 7) : "down",
203        head: isBlink ? "blink" : "look",
204        tail: cycle(["low", "high"], 18),
205        legs: "stand"
206      };
207    }
208    default:
209      return {
210        body: "still",
211        wing: cycle(a === "puff" ? ["up", "mid"] : fast, a === "roar" ? 2 : 3),
212        head: a !== "puff" || showLeft() > 12 ? "open" : "look",
213        tail: "high",
214        legs: a === "roar" ? cycle(["stepA", "stepB"], 3) : "stand"
215      };
216  }
217}
218function lift(a) {
219  if (a === "alert") {
220    const left = sim.alertUntil - sim.t;
221    return left > 12 ? -1 : left > 6 ? -2 : left > 4 ? -1 : 0;
222  }
223  if (a === "web") return cycle([-2, -2, -1, -1], 2);
224  if (a === "roar") return cycle([0, -1], 3);
225  return 0;
226}
227function shift(a) {
228  if (a === "edit") return cycle([0, 1, 2, 1], 3);
229  if (a === "search") return cycle([0, 1], 6);
230  return 0;
231}
232const glyph = (x, y, ch, life, vx = 0, vy = 0) => sim.particles.push({ x, y, vx, vy, age: 0, life, ch });
233const pixel = (x, y, vx, vy, life, wave = 0) => sim.particles.push({ x, y, vx, vy, age: 0, life, wave });
234function flame(count, reach) {
235  for (let i = 0; i < count; i++) {
236    pixel(MOUTH.x + sim.dx, MOUTH.y + sim.dy + rand(0, 1.4), rand(0.8, 1.6), rand(-0.15, 0.25), Math.floor(rand(reach * 0.5, reach)), rand(0, 6));
237  }
238}
239function step(a) {
240  stepBabies();
241  sim.dx = shift(a);
242  sim.dy = lift(a);
243  const top = DRAGON_WIDTH + 1;
244  switch (a) {
245    case "puff":
246      if (showLeft() > 12) flame(1, 3);
247      if (every(6)) pixel(NOSTRIL.x, NOSTRIL.y - 1, rand(0.1, 0.3), -0.2, 8);
248      if (showLeft() === SHOW_FRAMES.puff - 1) glyph(top, 0, "\u2713", 18);
249      break;
250    case "breath":
251      flame(2, 7);
252      break;
253    case "blaze":
254      flame(3, 8);
255      if (every(3)) glyph(rand(top, COLUMNS), rand(0, PIXEL_ROWS), pick(["*", "+", "\u2726"]), 5);
256      break;
257    case "roar":
258      flame(4, 8);
259      if (every(2)) glyph(rand(0, COLUMNS), rand(0, PIXEL_ROWS), pick(["*", "+", "\u2726", "."]), 6);
260      if (every(8)) "ROAR".split("").forEach((ch, i) => glyph(top + i, 0, ch, 5));
261      break;
262    case "sad":
263      if (every(10)) pixel(EYE.x, EYE.y + 2, 0, 0.25, 8);
264      break;
265    case "think":
266      if (every(8)) {
267        const k = Math.floor(sim.t / 8) % 3;
268        glyph(14 + k * 2, 0, [".", "o", "O"][k], 8);
269      }
270      break;
271    case "read":
272      if (every(3)) glyph(top - 1, 8, cycle(["=", "-", "="], 3), 3);
273      break;
274    case "search":
275      if (every(12)) glyph(23 + sim.dx, 0, "?", 12);
276      break;
277    case "edit":
278      if (every(2)) pixel(rand(8, 20) + sim.dx, PIXEL_ROWS - 1, rand(-0.5, 0.5), -0.25, 3);
279      break;
280    case "bash":
281      if (every(9)) pixel(NOSTRIL.x, NOSTRIL.y - 1, 0.25, -0.2, 6);
282      break;
283    case "web":
284      if (every(2)) glyph(rand(0, 6), rand(0, PIXEL_ROWS), "~", 6, -0.5, 0);
285      break;
286    case "sleep":
287      if (every(24)) glyph(top - 3, 1, pick(["z", "Z"]), 30, 0.12, -0.04);
288      break;
289    case "idle":
290      if (every(140)) pixel(NOSTRIL.x + 1, NOSTRIL.y - 1, 0.2, -0.15, 7);
291      break;
292  }
293  if (sim.t < sim.levelUntil && every(6)) {
294    `LV${sim.level}`.split("").forEach((ch, i) => glyph(top + 3 + i, 1, ch, 6));
295    glyph(rand(top, COLUMNS), rand(0, PIXEL_ROWS), "\u2726", 4);
296  }
297  for (const p of sim.particles) {
298    p.x += p.vx;
299    p.y += p.vy + (p.wave ? Math.sin((sim.t + p.wave) / 1.5) * 0.25 : 0);
300    p.age += 1;
301  }
302  sim.particles = sim.particles.filter((p) => p.age < p.life && p.x >= 0 && p.x < COLUMNS && p.y >= 0 && p.y < PIXEL_ROWS);
303}
304function freeSlot() {
305  for (let i = 0; i < SLOTS.length; i++) {
306    if (!sim.babies.some((b) => b.slot === i && b.state !== "home" && b.state !== "fall")) return i;
307  }
308  return -1;
309}
310function hatch(key, description, type, id, isNew = true) {
311  if (sim.babies.some((b) => b.key === key || id !== void 0 && b.id === id)) return;
312  sim.babies.push({ key, id, description, type, state: isNew ? "egg" : "fly", since: sim.t, slot: freeSlot(), lastTool: -100, y: 0 });
313}
314function stepBabies() {
315  for (const b of sim.babies) {
316    const age2 = sim.t - b.since;
317    if (b.state === "egg" && age2 >= 20) {
318      b.state = "fly";
319      b.since = sim.t;
320      glyph(slotOf(b).x + 2, slotOf(b).y + 2, "\u2726", 4);
321    }
322    if (b.state === "fall") b.y += 0.6;
323  }
324  sim.babies = sim.babies.filter((b) => {
325    if (b.state === "home" && sim.t - b.since >= 14) {
326      glyph(HOME.x, HOME.y, "\u2726", 6);
327      return false;
328    }
329    return !(b.state === "fall" && b.y > PIXEL_ROWS);
330  });
331  for (const b of sim.babies) if (b.slot < 0 && b.state === "fly") b.slot = freeSlot();
332}
333const slotOf = (b) => SLOTS[b.slot] ?? SLOTS[0];
334function drawBabies(ink, W5) {
335  let hidden = 0;
336  for (const b of sim.babies) {
337    if (b.slot < 0) {
338      hidden += 1;
339      continue;
340    }
341    const at = slotOf(b);
342    const age2 = sim.t - b.since;
343    if (b.state === "egg") {
344      const rock = age2 > 8 ? cycle([0, 1, 0, -1], 2) : 0;
345      const ex = at.x + 2 + rock;
346      const ey = at.y + 1;
347      [".#.", "###", "###"].forEach(
348        (row, dy) => [...row].forEach((c, dx) => {
349          if (c !== "#" || age2 > 15 && dx === 1 && dy === 1) return;
350          const x = ex + dx;
351          const y = ey + dy;
352          if (x >= 0 && x < W5 && y >= 0 && y < PIXEL_ROWS) ink[y * W5 + x] = 1;
353        })
354      );
355      continue;
356    }
357    const isBusy = sim.t - b.lastTool < 15;
358    const frame10 = cycle(["up", "down"], isBusy ? 2 : 4);
359    if (b.state === "home") {
360      const k = Math.min(1, age2 / 14);
361      drawBaby(ink, W5, at.x + (HOME.x - at.x) * k, at.y + (HOME.y - at.y) * k, frame10);
362      continue;
363    }
364    if (b.state === "fall") {
365      drawBaby(ink, W5, at.x, at.y + b.y, "down");
366      continue;
367    }
368    const bob = cycle(at.y === 0 ? [0, 0, 1, 1] : [0, 0, -1, -1], isBusy ? 2 : 5);
369    drawBaby(ink, W5, at.x, at.y + bob, frame10);
370  }
371  if (hidden > 0) glyph(W5 - 2, 8, `+${Math.min(9, hidden)}`.slice(-1), 2);
372}
373function frame(a) {
374  const W5 = COLUMNS;
375  const ink = new Uint8Array(W5 * PIXEL_ROWS);
376  const over = /* @__PURE__ */ new Map();
377  draw(ink, W5, pose(a), shift(a), lift(a));
378  drawBabies(ink, W5);
379  for (const p of sim.particles) {
380    const x = Math.round(p.x);
381    const y = Math.round(p.y);
382    if (x < 0 || x >= W5 || y < 0 || y >= PIXEL_ROWS) continue;
383    if (p.ch) {
384      over.set(Math.floor(y / 2) * W5 + x, p.ch);
385      continue;
386    }
387    if (p.age / p.life > 0.7 && (x + sim.t) % 2 === 0) continue;
388    ink[y * W5 + x] = 1;
389  }
390  const words = new Uint32Array(W5 * ROWS * 3);
391  for (let cy = 0; cy < ROWS; cy++) {
392    for (let cx = 0; cx < W5; cx++) {
393      const i = (cy * W5 + cx) * 3;
394      const top = ink[cy * 2 * W5 + cx] === 1;
395      const bottom = ink[(cy * 2 + 1) * W5 + cx] === 1;
396      const ch = over.get(cy * W5 + cx);
397      words[i] = ch ? ch.codePointAt(0) : top && bottom ? 9608 : top ? 9600 : bottom ? 9604 : 32;
398      words[i + 1] = INK;
399      words[i + 2] = NONE;
400    }
401  }
402  return base64(new Uint8Array(words.buffer));
403}
404const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
405function base64(bytes) {
406  let out = "";
407  let i = 0;
408  for (; i + 2 < bytes.length; i += 3) {
409    const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
410    out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + B64[n >> 6 & 63] + B64[n & 63];
411  }
412  const rest = bytes.length - i;
413  if (rest === 1) {
414    const n = bytes[i] << 16;
415    out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + "==";
416  } else if (rest === 2) {
417    const n = bytes[i] << 16 | bytes[i + 1] << 8;
418    out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + B64[n >> 6 & 63] + "=";
419  }
420  return out;
421}
422async function celebrate($, label, gold, show, isQuiet = false) {
423  sim.show = { kind: show, until: sim.t + SHOW_FRAMES[show] };
424  sim.lastActivity = sim.t;
425  const before = levelOf((await read2($, hoard)).gold);
426  const next = await update($, hoard, (old) => ({ ...old, gold: old.gold + gold, feats: old.feats + 1 }));
427  const after = levelOf(next.gold);
428  await update($, feat, () => `${label}: +${gold} gold`);
429  if (after > before) {
430    sim.level = after;
431    sim.levelUntil = sim.show.until + 45;
432    void notify($, `\u{1F525} ${label}! +${gold} gold. The dragon grows to level ${after}!`);
433  } else if (!isQuiet) {
434    void notify($, `\u{1F525} ${label}! The dragon hoards +${gold} gold`);
435  }
436}
437const ROARS = /* @__PURE__ */ new Set(["merge", "release", "deploy", "streak", "record", "squad"]);
438async function onMilestone($, tier, kind, label) {
439  if (tier === "small") await celebrate($, label, 1, "puff", true);
440  else if (tier === "medium") await celebrate($, label, 5, "breath");
441  else if (ROARS.has(kind)) await celebrate($, label, 50, "roar");
442  else await celebrate($, label, 25, "blaze");
443}
444async function celebrateMoments($, found) {
445  for (const m of found) await onMilestone($, m.tier, m.kind, m.label);
446}
447const start = async ($, e, next) => {
448  await $.command.register({
449    name: "dragon",
450    description: 'The dragon above the prompt: its hoard. "/dragon puff|fire|blaze|roar" to show off.'
451  });
452  $.clock.every(FPS_MS, () => {
453    sim.t += 1;
454    const a = activity();
455    const m = moodOf(a);
456    if (m !== sim.mood) {
457      sim.mood = m;
458      void update($, mood, () => m);
459    }
460    const requestId = sim.requestId;
461    if (requestId === null) return;
462    step(a);
463    if (sim.isBlitting) return;
464    sim.isBlitting = true;
465    void $.ui.blit({ requestId, key: RASTER, cells: frame(a) }).then((r) => {
466      if (r.deny !== void 0) sim.requestId = null;
467    }).finally(() => {
468      sim.isBlitting = false;
469    });
470  });
471  $.clock.every(1e3, () => {
472    void $.agent.list().then(
473      async (agents) => {
474        for (const b of sim.babies) {
475          if (b.id === void 0) {
476            const match = agents.find(
477              (a) => a.description === b.description && a.type === b.type && !sim.babies.some((o) => o.id === a.id)
478            );
479            if (match) b.id = match.id;
480          }
481        }
482        for (const a of agents) {
483          const isLive = a.status === "pending" || a.status === "running" || a.status === "waiting";
484          if (isLive && !sim.babies.some((b) => b.id === a.id)) hatch(`agent:${a.id}`, a.description, a.type, a.id);
485        }
486        for (const b of sim.babies) {
487          if (b.state !== "egg" && b.state !== "fly") continue;
488          const a = agents.find((x) => x.id === b.id);
489          const isGone = a === void 0 && sim.t - b.since > 30 * 1e3 / FPS_MS;
490          if (a?.status === "completed" || a?.status === "idle" || isGone) {
491            b.state = "home";
492            b.since = sim.t;
493            const fed = await update($, hoard, (old) => ({ ...old, gold: old.gold + 2 }));
494          } else if (a?.status === "failed" || a?.status === "killed") {
495            b.state = "fall";
496            b.since = sim.t;
497            b.y = 0;
498            sim.sadUntil = sim.t + 15;
499          }
500        }
501      },
502      () => void 0
503    );
504  });
505  return next(e);
506};
507const command = async ($, e) => {
508  const arg = (e.args ?? "").trim();
509  const practice = { puff: "puff", fire: "breath", blaze: "blaze", roar: "roar" };
510  const show = practice[arg];
511  if (show) {
512    await celebrate($, "Practice", 1, show);
513    return { text: "The dragon breathes fire." };
514  }
515  const last2 = await read2($, feat);
516  return { text: `${statsLine(await read2($, hoard))}${last2 ? `
517Last win: ${last2}` : ""}` };
518};
519const prompt = async ($, e, next) => {
520  sim.isTurn = true;
521  sim.alertUntil = sim.t + 15;
522  sim.lastActivity = sim.t;
523  const ran = await next(e);
524  return ran;
525};
526const turn = async ($, e, next) => {
527  sim.isTurn = false;
528  sim.lastActivity = sim.t;
529  const ran = await next(e);
530  return ran;
531};
532const tool = async ($, e, next) => {
533  if (e.agentId !== void 0) {
534    const baby = sim.babies.find((b) => b.id === e.agentId);
535    if (baby) baby.lastTool = sim.t;
536    return next(e);
537  }
538  const job = { id: e.tool_use_id, work: workOf(String(e.tool)) };
539  sim.working.push(job);
540  sim.lastActivity = sim.t;
541  const ran = await next(e).finally(() => {
542    sim.working = sim.working.filter((w) => w !== job);
543    sim.lastActivity = sim.t;
544  });
545  if (ran.deny !== void 0) return ran;
546  const snack = await update($, hoard, (old) => ({ ...old, meals: old.meals + 1 }));
547  if (ran.isError === true) {
548    sim.sadUntil = sim.t + 30;
549    return ran;
550  }
551  return ran;
552};
553const render = async ($, e, next) => {
554  const below = await next(e);
555  if (e.surface !== "terminal" || e.props.hasSurvey || !await isShown($, ID)) {
556    sim.requestId = null;
557    return below;
558  }
559  const { Box, Raster, Text } = $.ui.resolve(e);
560  sim.requestId = e.requestId;
561  const stash = await read2($, hoard);
562  sim.level = levelOf(stash.gold);
563  return <Box flexDirection="row" alignItems="flex-end">
564      <Box flexGrow={1} flexDirection="column">
565        {below ?? null}
566      </Box>
567      <Box flexDirection="column" flexShrink={0} minWidth={COLUMNS}>
568        <Raster key={RASTER} columns={COLUMNS} rows={ROWS} cells={frame(activity())} />
569        <Text key="stats" dimColor wrap="truncate">
570          {centred(statsLine(stash))}
571        </Text>
572      </Box>
573    </Box>;
574};
575async function reset($) {
576  await update($, hoard, () => hoard.initial);
577  await update($, feat, () => "");
578  sim.level = 1;
579}
580const game = { id: ID, title: "Dragon Lair" };
581
582// src/games/duck-hunt.tsx
583import { read as read3, update as update2, atom as atom3 } from "claude-code";
584const ID2 = "duck";
585const RASTER2 = "marsh";
586const ROWS2 = 8;
587const PH = ROWS2 * 2;
588const FPS_MS2 = 66;
589const MIN_COLUMNS = 24;
590const MAX_COLUMNS = 512;
591const SLEEP_AFTER = 2 * 60 * 1e3 / FPS_MS2;
592const INK2 = 16777216;
593const NONE2 = 16777216;
594const EMPTY = 4294967295;
595const GRASS = 4170573;
596const GRASS_DARK = 2849335;
597const TREE = 2849335;
598const TRUNK = 9132587;
599const DOG = 13141834;
600const DOG_DARK = 8014372;
601const DUCK = 9132587;
602const DUCK_HEAD = 3050327;
603const BEAK = 16756736;
604const SHOT = 14692657;
605const GREY = 9079434;
606const FEATHER = [16777215, 14272416, 9132587];
607const GROUND = PH - 3;
608const SKY_BOTTOM = GROUND - 4;
609const score = atom3({ plugin: "arcade", key: "duckScore" }, { hits: 0, escaped: 0, tools: 0 });
610const feat2 = atom3({ plugin: "arcade", key: "duckFeat" }, "");
611async function notify2($, text) {
612  if (await isShown($, ID2)) $.ui.toast(text);
613}
614const DUCK_UP = [".##.....", "..##..##", ".#######", "..####.."];
615const DUCK_DOWN = ["......##", ".#######", "..####..", "..##...."];
616const DUCK_HEAD_PIXELS = [[6, 0], [7, 0], [6, 1]];
617const DUCK_FALL = ["..#..#..", "..####..", ".######.", "..#..#.."];
618const DUCK_HIT = [".#....#.", ".######.", "..####..", "..#..#.."];
619const DOG_WALK = [
620  ["......##.", ".....####", "#...####.", "#######..", ".######..", ".#.#..#.#"],
621  ["......##.", ".....####", "#...####.", "#######..", ".######..", "#..#.#..#"]
622];
623const DOG_UP = ["..#####..", ".##.#.##.", ".#######.", "..#...#..", "..#####..", "...###..."];
624const DOG_LAUGH = ["..#####..", ".##.#.##.", ".#######.", "..#.#.#..", "..#####..", "...###..."];
625const DOG_SLEEP = [".........", ".........", ".........", "....##...", "#.######.", "########."];
626const SHOW_FRAMES2 = { shot: 18, hunt: 110, double: 140, perfect: 170 };
627const sim2 = {
628  t: 0,
629  W: 0,
630  requestId: null,
631  isBlitting: false,
632  isTurn: false,
633  working: 0,
634  lastActivity: 0,
635  show: null,
636  ducks: [],
637  particles: [],
638  banner: null,
639  dogX: 4,
640  dogFace: 1,
641  // The dog behind the grass, holding up what fell: 0 nothing, 1 or 2 ducks, -1 laughing.
642  catch: null,
643  laughUntil: -1,
644  crosshair: null,
645  nextDuck: 60,
646  gain: { hits: 0, escaped: 0 },
647  stats: "",
648  tree: 0,
649  round: 1
650};
651const rand2 = (a, b) => a + Math.random() * (b - a);
652const pick2 = (list2) => list2[Math.floor(Math.random() * list2.length)];
653const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
654const roundOf = (hits) => Math.floor(hits / 10) + 1;
655function fit(W5) {
656  if (W5 === sim2.W) return;
657  sim2.W = W5;
658  sim2.tree = Math.max(2, Math.floor(W5 * 0.72));
659  sim2.dogX = clamp(sim2.dogX, 0, W5 - 10);
660  sim2.ducks = sim2.ducks.filter((d) => d.x < W5);
661}
662function banner(text, color, frames) {
663  sim2.banner = { text, color, until: sim2.t + frames };
664}
665const glyph2 = (x, y, ch, life, color) => sim2.particles.push({ x, y, vx: 0, vy: 0, age: 0, life, color, ch });
666function feathers(x, y) {
667  for (let i = 0; i < 6; i++) {
668    sim2.particles.push({ x, y, vx: rand2(-0.6, 0.6), vy: rand2(-0.6, 0.1), age: 0, life: Math.floor(rand2(10, 22)), color: pick2(FEATHER), gravity: 0.04 });
669  }
670}
671function launch(isGame) {
672  const x = clamp(Math.floor(rand2(4, sim2.W - 12)), 0, Math.max(0, sim2.W - 9));
673  const duck = { x, y: SKY_BOTTOM, vx: pick2([-1, 1]) * rand2(0.5, 0.9), vy: -rand2(0.25, 0.45), state: "fly", since: sim2.t, isGame };
674  sim2.ducks.push(duck);
675  return duck;
676}
677function target() {
678  return sim2.ducks.find((d) => d.state === "fly") ?? launch(true);
679}
680function shoot(duck, counts = true) {
681  if (counts) duck.isGame = true;
682  duck.state = "hit";
683  duck.since = sim2.t;
684  sim2.crosshair = { x: Math.round(duck.x + 3), y: Math.round(duck.y + 1), until: sim2.t + 10 };
685  feathers(duck.x + 4, duck.y + 1);
686}
687function stepDucks() {
688  for (const d of sim2.ducks) {
689    const age2 = sim2.t - d.since;
690    if (d.state === "fly") {
691      d.x += d.vx;
692      d.y += d.vy;
693      if (d.x < 0 || d.x > sim2.W - 9) d.vx = -d.vx;
694      if (d.y < 0 || d.y > SKY_BOTTOM) d.vy = -d.vy;
695      if (Math.random() < 0.02) d.vy = -d.vy;
696      if (age2 > 260) d.state = "away";
697    } else if (d.state === "hit" && age2 > 8) {
698      d.state = "fall";
699      d.since = sim2.t;
700    } else if (d.state === "fall") {
701      d.y += 0.7;
702      if (d.y >= GROUND) {
703        d.y = PH;
704        const caught = sim2.catch && sim2.t - sim2.catch.since < 10 ? sim2.catch.ducks + 1 : 1;
705        sim2.catch = { ducks: Math.min(2, caught), x: clamp(Math.round(d.x), 0, sim2.W - 9), since: sim2.t };
706        if (d.isGame) sim2.gain.hits += 1;
707      }
708    } else if (d.state === "away") {
709      d.y -= 0.8;
710      d.x += d.vx;
711    }
712  }
713  sim2.ducks = sim2.ducks.filter((d) => d.y > -5 && d.y < PH && d.x > -9 && d.x < sim2.W + 1);
714}
715function flyAway(isPractice) {
716  const duck = sim2.ducks.find((d) => d.state === "fly");
717  if (duck) {
718    duck.state = "away";
719    duck.since = sim2.t;
720    if (!isPractice) sim2.gain.escaped += 1;
721    banner("FLY AWAY", GREY, 30);
722  }
723  sim2.laughUntil = sim2.t + 40;
724}
725function step2() {
726  sim2.t += 1;
727  const show = sim2.show && sim2.t < sim2.show.until ? sim2.show : null;
728  const age2 = show ? SHOW_FRAMES2[show.kind] - (show.until - sim2.t) : 0;
729  if (show) {
730    if (show.kind === "shot" && age2 === 1) {
731      const duck = sim2.ducks.find((d) => d.state === "fly");
732      if (duck) shoot(duck, false);
733      else {
734        const x = Math.floor(rand2(4, sim2.W - 8));
735        sim2.crosshair = { x, y: Math.floor(rand2(2, SKY_BOTTOM)), until: sim2.t + 6 };
736        glyph2(x, Math.floor(rand2(0, 4)), "*", 6, SHOT);
737      }
738    }
739    if (show.kind === "hunt" && age2 === 1) target();
740    if (show.kind === "hunt" && age2 === 25) shoot(target());
741    if ((show.kind === "double" || show.kind === "perfect") && age2 === 1) {
742      launch(true);
743      launch(true);
744    }
745    if ((show.kind === "double" || show.kind === "perfect") && (age2 === 25 || age2 === 35)) shoot(target());
746    if (show.kind === "double" && age2 === 40) banner("DOUBLE!", SHOT, 50);
747    if (show.kind === "perfect" && age2 === 40) banner("PERFECT!", BEAK, 70);
748    if (show.kind === "perfect" && age2 > 40 && age2 % 6 === 0) feathers(rand2(2, sim2.W - 2), rand2(0, 6));
749  }
750  const isAsleep = !sim2.isTurn && sim2.working === 0 && sim2.t - sim2.lastActivity > SLEEP_AFTER;
751  if (!isAsleep && !sim2.catch && sim2.laughUntil < sim2.t) {
752    const speed2 = sim2.working > 0 ? 0.5 : sim2.isTurn ? 0.3 : 0.1;
753    sim2.dogX += sim2.dogFace * speed2;
754    if (sim2.dogX < 0 || sim2.dogX > sim2.W - 10) sim2.dogFace = -sim2.dogFace;
755  }
756  if ((sim2.isTurn || sim2.working > 0) && !show && sim2.t >= sim2.nextDuck && !sim2.ducks.some((d) => d.state === "fly")) {
757    launch(false);
758    sim2.nextDuck = sim2.t + Math.floor(rand2(90, 200));
759  }
760  stepDucks();
761  if (sim2.catch && sim2.t - sim2.catch.since > 45) sim2.catch = null;
762  for (const p of sim2.particles) {
763    p.age += 1;
764    p.x += p.vx;
765    p.y += p.vy;
766    p.vy += p.gravity ?? 0;
767  }
768  sim2.particles = sim2.particles.filter((p) => p.age < p.life && p.x >= 0 && p.x < sim2.W && p.y >= 0 && p.y < PH);
769  return isAsleep;
770}
771function put(buf, x, y, color) {
772  x = Math.round(x);
773  y = Math.round(y);
774  if (x < 0 || x >= sim2.W || y < 0 || y >= PH) return;
775  buf[y * sim2.W + x] = color;
776}
777function mask(buf, rows, x, y, color, isFlipped = false) {
778  rows.forEach((row, dy) => {
779    const w = row.length;
780    for (let dx = 0; dx < w; dx++) {
781      if (row[isFlipped ? w - 1 - dx : dx] === "#") put(buf, x + dx, y + dy, color);
782    }
783  });
784}
785function drawDuck(buf, d) {
786  const isLeft = d.vx < 0;
787  if (d.state === "hit") return mask(buf, DUCK_HIT, d.x, d.y, DUCK, isLeft);
788  if (d.state === "fall") return mask(buf, DUCK_FALL, d.x, d.y, DUCK, isLeft);
789  const isUp = Math.floor(sim2.t / 4) % 2 === 0;
790  mask(buf, isUp ? DUCK_UP : DUCK_DOWN, d.x, d.y, DUCK, isLeft);
791  for (const [hx, hy] of DUCK_HEAD_PIXELS) put(buf, isLeft ? d.x + 7 - hx : d.x + hx, d.y + hy, DUCK_HEAD);
792  put(buf, isLeft ? d.x - 1 : d.x + 8, d.y + (isUp ? 1 : 0), BEAK);
793}
794function drawScene(buf) {
795  const tx = sim2.tree;
796  for (let y = GROUND - 7; y < GROUND; y++) {
797    const half = y < GROUND - 4 ? 3 - Math.abs(GROUND - 6 - y) : 0;
798    for (let dx = -half; dx <= half; dx++) put(buf, tx + dx, y, TREE);
799  }
800  for (let y = GROUND - 4; y < GROUND; y++) put(buf, tx, y, TRUNK);
801  for (let x = 0; x < sim2.W; x++) {
802    for (let y = GROUND; y < PH; y++) put(buf, x, y, GRASS);
803    if (x * 7 % 5 < 2) put(buf, x, GROUND - 1, GRASS_DARK);
804  }
805}
806function drawDog(buf, isAsleep) {
807  if (sim2.catch) {
808    const rise = Math.min(7, Math.floor((sim2.t - sim2.catch.since) / 2));
809    const y2 = PH - 1 - rise;
810    mask(buf, DOG_UP, sim2.catch.x, y2, DOG);
811    for (let i = 0; i < sim2.catch.ducks; i++) mask(buf, DUCK_FALL, sim2.catch.x + (i === 0 ? -3 : 5), y2 - 3, DUCK);
812    return;
813  }
814  if (sim2.laughUntil > sim2.t) {
815    const bob = Math.floor(sim2.t / 3) % 2;
816    mask(buf, DOG_LAUGH, sim2.dogX, PH - 8 + bob, DOG);
817    return;
818  }
819  const y = GROUND - 5;
820  if (isAsleep) {
821    mask(buf, DOG_SLEEP, sim2.dogX, y, DOG);
822    return;
823  }
824  const legs = DOG_WALK[Math.floor(sim2.t / 4) % 2];
825  mask(buf, legs, sim2.dogX, y, DOG, sim2.dogFace < 0);
826  put(buf, sim2.dogFace < 0 ? sim2.dogX + 2 : sim2.dogX + 6, y, DOG_DARK);
827}
828const statsLine2 = (s) => `R ${roundOf(s.hits)}  \u25BC ${s.hits}  \u2197 ${s.escaped}  \u2692 ${s.tools}`;
829function frame2(isAsleep, stats) {
830  const W5 = sim2.W;
831  const buf = new Uint32Array(W5 * PH).fill(EMPTY);
832  const over = /* @__PURE__ */ new Map();
833  const text = (x, row, s, color) => [...s].forEach((ch, i) => {
834    if (x + i >= 0 && x + i < W5) over.set(row * W5 + x + i, { ch, color });
835  });
836  for (const d of sim2.ducks) drawDuck(buf, d);
837  const isBehind = sim2.catch !== null || sim2.laughUntil > sim2.t;
838  if (isBehind) drawDog(buf, isAsleep);
839  drawScene(buf);
840  if (!isBehind) drawDog(buf, isAsleep);
841  if (sim2.crosshair && sim2.t < sim2.crosshair.until) {
842    const { x, y } = sim2.crosshair;
843    for (const [dx, dy] of [[-2, 0], [2, 0], [0, -2], [0, 2], [-1, 0], [1, 0], [0, -1], [0, 1]]) put(buf, x + dx, y + dy, SHOT);
844  }
845  if (isAsleep) text(Math.round(sim2.dogX) + 9, Math.floor((GROUND - 4) / 2), "z", GREY);
846  for (const p of sim2.particles) {
847    if (p.ch) text(Math.round(p.x), Math.floor(Math.round(p.y) / 2), p.ch, p.color);
848    else put(buf, p.x, p.y, p.color);
849  }
850  text(W5 - stats.length - 1, 0, stats, GREY);
851  if (sim2.banner && sim2.t < sim2.banner.until && (sim2.banner.until - sim2.t) % 8 > 1) {
852    text(Math.floor((W5 - sim2.banner.text.length) / 2), 1, sim2.banner.text, sim2.banner.color);
853  }
854  const words = new Uint32Array(W5 * ROWS2 * 3);
855  for (let cy = 0; cy < ROWS2; cy++) {
856    for (let cx = 0; cx < W5; cx++) {
857      const i = (cy * W5 + cx) * 3;
858      const top = buf[cy * 2 * W5 + cx];
859      const bottom = buf[(cy * 2 + 1) * W5 + cx];
860      const g = over.get(cy * W5 + cx);
861      if (g) {
862        words[i] = g.ch.codePointAt(0);
863        words[i + 1] = g.color;
864        words[i + 2] = NONE2;
865      } else if (top === EMPTY && bottom === EMPTY) {
866        words[i] = 32;
867        words[i + 1] = INK2;
868        words[i + 2] = NONE2;
869      } else if (top === bottom) {
870        words[i] = 9608;
871        words[i + 1] = top;
872        words[i + 2] = NONE2;
873      } else if (bottom === EMPTY) {
874        words[i] = 9600;
875        words[i + 1] = top;
876        words[i + 2] = NONE2;
877      } else if (top === EMPTY) {
878        words[i] = 9604;
879        words[i + 1] = bottom;
880        words[i + 2] = NONE2;
881      } else {
882        words[i] = 9600;
883        words[i + 1] = top;
884        words[i + 2] = bottom;
885      }
886    }
887  }
888  return base642(new Uint8Array(words.buffer));
889}
890const B642 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
891function base642(bytes) {
892  let out = "";
893  let i = 0;
894  for (; i + 2 < bytes.length; i += 3) {
895    const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
896    out += B642[n >> 18 & 63] + B642[n >> 12 & 63] + B642[n >> 6 & 63] + B642[n & 63];
897  }
898  const rest = bytes.length - i;
899  if (rest === 1) {
900    const n = bytes[i] << 16;
901    out += B642[n >> 18 & 63] + B642[n >> 12 & 63] + "==";
902  } else if (rest === 2) {
903    const n = bytes[i] << 16 | bytes[i + 1] << 8;
904    out += B642[n >> 18 & 63] + B642[n >> 12 & 63] + B642[n >> 6 & 63] + "=";
905  }
906  return out;
907}
908async function save($, change2) {
909  const next = await update2($, score, change2);
910  sim2.stats = statsLine2(next);
911  return next;
912}
913const RANK = { shot: 0, hunt: 1, double: 2, perfect: 3 };
914async function celebrate2($, label, show, isQuiet = false) {
915  const running = sim2.show && sim2.t < sim2.show.until ? sim2.show.kind : null;
916  if (running === null || RANK[show] >= RANK[running]) sim2.show = { kind: show, until: sim2.t + SHOW_FRAMES2[show] };
917  sim2.lastActivity = sim2.t;
918  const what = { shot: "a shot", hunt: "a duck down", double: "a double", perfect: "a perfect round" };
919  await update2($, feat2, () => `${label}: ${what[show]}`);
920  if (!isQuiet) {
921    const say2 = {
922      shot: "Bang",
923      hunt: "Got one",
924      double: "Two in one round",
925      perfect: "Perfect"
926    };
927    void notify2($, `\u{1F986} ${label}! ${say2[show]}!`);
928  }
929}
930const PERFECT = /* @__PURE__ */ new Set(["merge", "release", "deploy", "streak", "record", "squad"]);
931async function onMilestone2($, tier, kind, label) {
932  if (tier === "small") await celebrate2($, label, "shot", true);
933  else if (tier === "medium") await celebrate2($, label, "hunt");
934  else if (PERFECT.has(kind)) await celebrate2($, label, "perfect");
935  else await celebrate2($, label, "double");
936}
937async function celebrateMoments2($, found) {
938  for (const m of found) await onMilestone2($, m.tier, m.kind, m.label);
939}
940const start2 = async ($, e, next) => {
941  const s = await read3($, score);
942  sim2.stats = statsLine2(s);
943  sim2.round = roundOf(s.hits);
944  await $.command.register({
945    name: "duck",
946    description: 'The duck hunt above the prompt: the score. "/duck shot|hunt|double|perfect|flyaway" to show off.'
947  });
948  $.clock.every(FPS_MS2, () => {
949    const requestId = sim2.requestId;
950    if (sim2.gain.hits > 0 || sim2.gain.escaped > 0) {
951      const gain = sim2.gain;
952      sim2.gain = { hits: 0, escaped: 0 };
953      void save($, (old) => ({ ...old, hits: old.hits + gain.hits, escaped: old.escaped + gain.escaped })).then((s2) => {
954        const round = roundOf(s2.hits);
955        if (round > sim2.round) {
956          sim2.round = round;
957          banner(`ROUND ${round}`, BEAK, 60);
958          void notify2($, `\u{1F986} Round ${round}: ${s2.hits} ducks down.`);
959        }
960      });
961    }
962    if (requestId === null || sim2.W === 0) return;
963    const isAsleep = step2();
964    if (sim2.isBlitting) return;
965    sim2.isBlitting = true;
966    void $.ui.blit({ requestId, key: RASTER2, cells: frame2(isAsleep, sim2.stats), columns: sim2.W }).then((r) => {
967      if (r.deny !== void 0) sim2.requestId = null;
968    }).finally(() => {
969      sim2.isBlitting = false;
970    });
971  });
972  return next(e);
973};
974const command2 = async ($, e) => {
975  const arg = (e.args ?? "").trim();
976  const practice = { shot: "shot", hunt: "hunt", double: "double", perfect: "perfect" };
977  const show = practice[arg];
978  if (show) {
979    await celebrate2($, "Practice", show, true);
980    return { text: "Practice: nothing counts." };
981  }
982  if (arg === "flyaway") {
983    flyAway(true);
984    return { text: "The duck gets away, and the dog laughs." };
985  }
986  const last2 = await read3($, feat2);
987  return {
988    text: `${statsLine2(await read3($, score))}${last2 ? `
989Last win: ${last2}` : ""}
990A medium moment (a commit, a skill, a sent message) shoots a duck down, a big one (a merge, a deploy, a finished task list) shoots two; a failed tool lets one get away. R is the round (ten ducks each), \u25BC ducks down, \u2197 ducks that got away, \u2692 tool calls.`
991  };
992};
993const prompt2 = async ($, e, next) => {
994  sim2.isTurn = true;
995  sim2.lastActivity = sim2.t;
996  return next(e);
997};
998const turn2 = async ($, e, next) => {
999  if (e.agentId === void 0) sim2.isTurn = false;
1000  sim2.lastActivity = sim2.t;
1001  return next(e);
1002};
1003const tool2 = async ($, e, next) => {
1004  if (e.agentId !== void 0) return next(e);
1005  sim2.working += 1;
1006  sim2.lastActivity = sim2.t;
1007  const ran = await next(e).finally(() => {
1008    sim2.working = Math.max(0, sim2.working - 1);
1009    sim2.lastActivity = sim2.t;
1010  });
1011  if (ran.deny !== void 0) return ran;
1012  const counted = await update2($, score, (old) => ({ ...old, tools: old.tools + 1 }));
1013  sim2.stats = statsLine2(counted);
1014  if (ran.isError === true) flyAway(false);
1015  return ran;
1016};
1017const render2 = async ($, e, next) => {
1018  const below = await next(e);
1019  if (e.surface !== "terminal" || e.props.hasSurvey || !await isShown($, ID2)) {
1020    sim2.requestId = null;
1021    return below;
1022  }
1023  const { Box, Raster } = $.ui.resolve(e);
1024  fit(clamp(e.props.bodyColumns, MIN_COLUMNS, MAX_COLUMNS));
1025  sim2.requestId = e.requestId;
1026  sim2.stats = statsLine2(await read3($, score));
1027  return <Box flexDirection="column">
1028      <Raster key={RASTER2} columns={sim2.W} rows={ROWS2} cells={frame2(false, sim2.stats)} />
1029      {below ?? null}
1030    </Box>;
1031};
1032async function reset2($) {
1033  const next = await update2($, score, () => score.initial);
1034  sim2.stats = statsLine2(next);
1035  sim2.round = 1;
1036  await update2($, feat2, () => "");
1037}
1038const game2 = { id: ID2, title: "Duck Hunt" };
1039
1040// src/games/jackpot.tsx
1041import { atom as atom4, read as read4, update as update3 } from "claude-code";
1042
1043// src/games/jackpot-symbols.ts
1044const PALETTE = {
1045  R: 16726862,
1046  r: 11735594,
1047  Y: 16765773,
1048  y: 13211164,
1049  G: 5034109,
1050  g: 2328394,
1051  C: 6281471,
1052  c: 2789311,
1053  W: 16777215,
1054  P: 12614655,
1055  O: 16751165,
1056  K: 1381661
1057};
1058const SYMBOLS = {
1059  seven: ["RRRRRR", "RRRRRR", "...RR.", "..RR..", "..RR..", "..RR.."],
1060  dragon: ["G....G", "GG..GG", "GGGGGG", "G.GG.G", "GGGGGG", ".GGGG."],
1061  diamond: ["..CC..", ".CCCC.", "CCCCCC", "CCCCCC", ".CCCC.", "..CC.."],
1062  bell: ["..YY..", ".YYYY.", ".YYYY.", "YYYYYY", "......", "..YY.."],
1063  star: ["..PP..", "..PP..", "PPPPPP", ".PPPP.", ".P..P.", "P....P"],
1064  cherry: ["...GG.", "..G.G.", ".G..G.", "RR.RR.", "RR.RR.", "......"]
1065};
1066const SYMBOL_SIZE = 6;
1067
1068// src/games/jackpot.tsx
1069const ID3 = "jackpot";
1070const RASTER3 = "machine";
1071const FPS_MS3 = 66;
1072const W = 28;
1073const H = 12;
1074const ROWS3 = H / 2;
1075const TOP = 3;
1076const WINDOW = SYMBOL_SIZE + 1;
1077const REEL_W = 8;
1078const PERIOD = SYMBOL_SIZE + 2;
1079const CLEAR = -1;
1080const DEFAULT = 16777216;
1081const bank = atom4({ plugin: "arcade", key: "jackpotBank" }, { chips: 0, spins: 0, jackpots: 0, best: 0, streak: 0 });
1082const golden = atom4({ plugin: "arcade", key: "jackpotGolden" }, 0);
1083const last = atom4({ plugin: "arcade", key: "jackpotLast" }, "");
1084async function notify3($, text) {
1085  if (!!await isShown($, ID3)) $.ui.toast(text);
1086}
1087const ORDER = ["seven", "dragon", "diamond", "bell", "star", "cherry"];
1088const TRIPLE_PAY = { seven: 100, dragon: 50, diamond: 25, bell: 15, star: 10, cherry: 8 };
1089const STRIPS = [
1090  ["cherry", "star", "seven", "bell", "cherry", "diamond", "star", "dragon", "bell", "cherry", "star", "diamond"],
1091  ["star", "cherry", "bell", "dragon", "cherry", "star", "seven", "diamond", "cherry", "bell", "star", "diamond"],
1092  ["bell", "diamond", "cherry", "star", "seven", "cherry", "bell", "star", "dragon", "cherry", "diamond", "star"]
1093];
1094const sim3 = {
1095  t: 0,
1096  queue: [],
1097  spin: null,
1098  reels: [0, 0, 0].map((_, i) => ({ from: i * 2 * PERIOD, to: i * 2 * PERIOD, start: -100, frames: 1, landedAt: -100 })),
1099  result: ["seven", "seven", "seven"],
1100  win: "none",
1101  winUntil: -1,
1102  payout: 0,
1103  coins: [],
1104  hadError: false,
1105  requestId: null,
1106  isBlitting: false,
1107  isLooping: false
1108};
1109const rand3 = (a, b) => a + Math.random() * (b - a);
1110const pick3 = (list2) => list2[Math.floor(Math.random() * list2.length)];
1111const cycle2 = (frames, n) => frames[Math.floor(sim3.t / n) % frames.length];
1112const multiplier = (streak) => Math.min(5, 1 + Math.floor(streak / 5));
1113function outcome(isGolden, roll = Math.random()) {
1114  const odds = isGolden ? { jackpot: 1 / 20, dragon: 1 / 10, triple: 1 / 4, pair: 1 } : { jackpot: 1 / 200, dragon: 1 / 80, triple: 1 / 15, pair: 1 / 3 };
1115  let edge = odds.jackpot;
1116  if (roll < edge) return ["seven", "seven", "seven"];
1117  edge += odds.dragon;
1118  if (roll < edge) return ["dragon", "dragon", "dragon"];
1119  edge += odds.triple;
1120  if (roll < edge) {
1121    const s = pick3(["diamond", "bell", "star", "cherry"]);
1122    return [s, s, s];
1123  }
1124  edge += odds.pair;
1125  if (roll < edge) {
1126    const s = pick3(ORDER);
1127    const other2 = pick3(ORDER.filter((o) => o !== s));
1128    const at = Math.floor(Math.random() * 3);
1129    return [0, 1, 2].map((i) => i === at ? other2 : s);
1130  }
1131  if (Math.random() < 0.2) return ["seven", "seven", pick3(ORDER.filter((o) => o !== "seven"))];
1132  const [a, b, c] = [...ORDER].sort(() => Math.random() - 0.5);
1133  return [a, b, c];
1134}
1135function score2(symbols) {
1136  const [a, b, c] = symbols;
1137  if (a === b && b === c) return { win: a === "seven" ? "jackpot" : "triple", pay: TRIPLE_PAY[a] };
1138  if (a === b || b === c || a === c) {
1139    const pair = a === b || a === c ? a : b;
1140    return { win: "pair", pay: pair === "cherry" ? 3 : 2 };
1141  }
1142  return { win: "none", pay: 0 };
1143}
1144function startSpin(spin) {
1145  sim3.spin = spin;
1146  sim3.win = "none";
1147  sim3.result = spin.forced ?? outcome(spin.isGolden);
1148  sim3.reels = sim3.reels.map((reel, i) => {
1149    const strip = STRIPS[i];
1150    const len = strip.length * PERIOD;
1151    const now = (reel.to % len + len) % len;
1152    const index = strip.indexOf(sim3.result[i]);
1153    const target2 = (2 + i) * len + index * PERIOD;
1154    return { from: now, to: target2, start: sim3.t, frames: 14 + i * 7, landedAt: -100 };
1155  });
1156}
1157function reelPos(reel, i) {
1158  const k = Math.min(1, (sim3.t - reel.start) / reel.frames);
1159  if (k >= 1) {
1160    if (reel.landedAt < 0) reel.landedAt = sim3.t;
1161    const since = sim3.t - reel.landedAt;
1162    return reel.to + ([-2, -1, 1, 0][since] ?? 0);
1163  }
1164  void i;
1165  return reel.from + (reel.to - reel.from) * (1 - (1 - k) ** 3);
1166}
1167async function finishSpin($) {
1168  const spin = sim3.spin;
1169  sim3.spin = null;
1170  const { win, pay } = score2(sim3.result);
1171  const before = await read4($, bank);
1172  const mult = multiplier(before.streak) * (spin.isGolden ? 2 : 1);
1173  const payout = spin.isPractice ? 0 : pay * mult;
1174  sim3.win = win;
1175  sim3.payout = payout;
1176  sim3.winUntil = sim3.t + (win === "jackpot" ? 75 : win === "triple" ? 40 : win === "pair" ? 18 : 6);
1177  if (win === "triple" || win === "jackpot") {
1178    for (let i = 0; i < (win === "jackpot" ? 30 : 12); i++) {
1179      sim3.coins.push({ x: rand3(4, W - 4), y: TOP + 4, vx: rand3(-0.6, 0.6), vy: rand3(-1.4, -0.7), age: 0 });
1180    }
1181  }
1182  if (spin.isPractice) return;
1183  const next = await update3($, bank, (old) => ({
1184    ...old,
1185    chips: old.chips + payout,
1186    spins: old.spins + 1,
1187    jackpots: old.jackpots + (win === "jackpot" ? 1 : 0),
1188    best: Math.max(old.best, payout)
1189  }));
1190  const names = sim3.result.join(" ");
1191  await update3($, last, () => payout > 0 ? `${names}: +${payout}` : names);
1192  if (win === "jackpot") void notify3($, `\u{1F3B0} JACKPOT! 7 7 7 pays ${payout} chips`);
1193  else if (win === "triple") void notify3($, `\u{1F3B0} Three ${sim3.result[0]}s! +${payout} chips`);
1194}
1195function tick($) {
1196  sim3.t += 1;
1197  if (sim3.spin) {
1198    const isDone2 = sim3.reels.every((r) => sim3.t - r.start >= r.frames + 4);
1199    if (isDone2) void finishSpin($);
1200  } else if (sim3.t >= sim3.winUntil && sim3.queue.length > 0) {
hooks/bug-sky.js 354 lines
1// Built by scripts/build.sh from src/clients/ with esbuild@0.25.10. Do not edit: edit src/ and rebuild.
2// src/clients/bug-sky.tsx
3var ROWS = 8;
4var PH = ROWS * 2;
5var TICK_MS = 66;
6var GROUND = PH - 1;
7var CITY_TOP = PH - 4;
8var SKY_FLOOR = CITY_TOP - 1;
9var CITIES = 6;
10var SHOT_SPEED = 1.8;
11var COOLDOWN = 5;
12var SAND = "#c2a14d";
13var SILO = "#d9a441";
14var CITY = "#4dabf7";
15var RUBBLE = "#6b6b6b";
16var BUG_TRAIL = "#a8323e";
17var BUG_HEAD = "#ff6b6b";
18var SHOT_TRAIL = "#4c6ef5";
19var SHOT_HEAD = "#ffffff";
20var CROSS = "#69db7c";
21var GREY = "#8a8a8a";
22var GOLD = "#ffd43b";
23var BLAST = ["#ffffff", "#ffd43b", "#ff922b", "#f06595", "#cc5de8"];
24var rand = (a, b) => a + Math.random() * (b - a);
25var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
26function layout(W) {
27  const silos = [2, Math.floor(W / 2), W - 3];
28  const xs = [];
29  for (const [a, b] of [[silos[0], silos[1]], [silos[1], silos[2]]]) {
30    for (let i = 1; i <= 3; i++) xs.push(Math.round(a + (b - a) * i / 4) - 2);
31  }
32  return { silos, cityX: xs };
33}
34function create(props) {
35  const W = Math.max(24, props.columns);
36  const standing = clamp(props.cities, 0, CITIES);
37  return {
38    t: 0,
39    W,
40    seen: props.events.reduce((m, e) => Math.max(m, e.id), 0),
41    cities: Array.from({ length: CITIES }, (_, i) => i < standing || standing === 0),
42    silos: layout(W).silos,
43    cooldown: [0, 0, 0],
44    bugs: [],
45    shots: [],
46    blasts: [],
47    plans: [],
48    banner: null,
49    cross: { x: Math.floor(W / 2), y: 4 },
50    hasFired: false,
51    nextBug: 40,
52    endAt: -1,
53    gain: { kills: 0, mine: 0, lost: 0, ends: 0, cities: standing === 0 ? CITIES : standing },
54    props
55  };
56}
57var cityX = (s, i) => layout(s.W).cityX[i];
58function banner(s, text, color, frames) {
59  s.banner = { text, color, until: s.t + frames };
60}
61function dropBug(s, isReal, fast = false) {
62  const alive = s.cities.map((c, i) => c ? i : -1).filter((i) => i >= 0);
63  if (alive.length === 0) return;
64  const target = alive[Math.floor(Math.random() * alive.length)];
65  const x0 = rand(1, s.W - 2);
66  const tx = cityX(s, target) + 2;
67  const frames = fast ? rand(70, 100) : rand(130, 200);
68  const vy = (SKY_FLOOR + 2) / frames;
69  s.bugs.push({ x0, y0: 0, x: x0, y: 0, vx: (tx - x0) / frames, vy, target, isReal });
70}
71function nearestSilo(s, x) {
72  let best = -1;
73  for (let i = 0; i < s.silos.length; i++) {
74    if (s.cooldown[i] > 0) continue;
75    if (best < 0 || Math.abs(s.silos[i] - x) < Math.abs(s.silos[best] - x)) best = i;
76  }
77  return best;
78}
79function fire(s, tx, ty, owner, isMiss = false, silo = nearestSilo(s, tx)) {
80  if (silo < 0) return false;
81  const sx = s.silos[silo];
82  s.cooldown[silo] = owner === "you" ? COOLDOWN : 0;
83  s.shots.push({ x0: sx, y0: SKY_FLOOR + 1, x: sx, y: SKY_FLOOR + 1, tx: clamp(tx, 0, s.W - 1), ty: clamp(ty, 0, SKY_FLOOR), owner, isMiss });
84  return true;
85}
86function autoShot(s, bug, owner, isMiss) {
87  const silo = s.silos.reduce((b, x, i) => Math.abs(x - bug.x) < Math.abs(s.silos[b] - bug.x) ? i : b, 0);
88  let tx = bug.x;
89  let ty = bug.y;
90  for (let k = 0; k < 3; k++) {
91    const frames = Math.hypot(tx - s.silos[silo], ty - (SKY_FLOOR + 1)) / SHOT_SPEED;
92    tx = bug.x + bug.vx * frames;
93    ty = bug.y + bug.vy * frames;
94  }
95  if (isMiss) tx += (Math.random() < 0.5 ? -1 : 1) * rand(6, 9);
96  fire(s, tx, ty, owner, isMiss, silo);
97}
98var lowest = (s) => s.bugs.reduce((b, x) => b === void 0 || x.y > b.y ? x : b, void 0);
99function play(s, e) {
100  const owner = e.practice ? "practice" : "auto";
101  if (e.kind === "tool") {
102    const bug = lowest(s);
103    if (bug) autoShot(s, bug, owner, Math.random() < 0.4);
104  } else if (e.kind === "fail") {
105    dropBug(s, !e.practice, true);
106    banner(s, "INCOMING", BUG_HEAD, 30);
107  } else if (e.kind === "small") {
108    const bug = lowest(s);
109    if (bug) autoShot(s, bug, owner, false);
110    else s.blasts.push({ x: rand(4, s.W - 4), y: rand(2, 7), age: 0, max: 2, owner: "practice" });
111  } else if (e.kind === "medium") {
112    if (s.bugs.length === 0) dropBug(s, !e.practice);
113    s.plans.push({ at: s.t + 20, kind: "sure", owner });
114  } else {
115    if (s.bugs.length === 0) {
116      dropBug(s, !e.practice);
117      dropBug(s, !e.practice);
118    }
119    s.plans.push({ at: s.t + 20, kind: "salvo", owner });
120    const ruined = s.cities.findIndex((c) => !c);
121    if (ruined >= 0 && !e.practice) {
122      s.cities[ruined] = true;
123      s.gain.cities = s.cities.filter(Boolean).length;
124      banner(s, "BONUS CITY", GOLD, 60);
125    } else banner(s, "SKY CLEAR!", GOLD, 60);
126  }
127}
128function step(s) {
129  s.t += 1;
130  for (let i = 0; i < s.cooldown.length; i++) s.cooldown[i] = Math.max(0, s.cooldown[i] - 1);
131  for (const p of s.plans.filter((p2) => p2.at <= s.t)) {
132    if (p.kind === "sure") {
133      const bug = lowest(s);
134      if (bug) autoShot(s, bug, p.owner, false);
135    } else for (const bug of s.bugs) autoShot(s, bug, p.owner, false);
136  }
137  s.plans = s.plans.filter((p) => p.at > s.t);
138  if (s.props.working && s.endAt < 0 && s.t >= s.nextBug && s.bugs.length < 3) {
139    dropBug(s, true);
140    s.nextBug = s.t + Math.floor(rand(120, 260));
141  }
142  for (const shot of s.shots) {
143    const dx = shot.tx - shot.x;
144    const dy = shot.ty - shot.y;
145    const d = Math.hypot(dx, dy);
146    if (d <= SHOT_SPEED) {
147      shot.x = shot.tx;
148      shot.y = shot.ty;
149      s.blasts.push({ x: shot.tx, y: shot.ty, age: 0, max: 3, owner: shot.owner });
150    } else {
151      shot.x += dx / d * SHOT_SPEED;
152      shot.y += dy / d * SHOT_SPEED;
153    }
154  }
155  s.shots = s.shots.filter((shot) => shot.x !== shot.tx || shot.y !== shot.ty);
156  for (const bug of s.bugs) {
157    bug.x += bug.vx;
158    bug.y += bug.vy;
159  }
160  for (const b of s.blasts) b.age += 1;
161  const radius = (b) => {
162    const r = [1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1][b.age] ?? 0;
163    return Math.min(r, b.max);
164  };
165  const chained = [];
166  s.bugs = s.bugs.filter((bug) => {
167    const hit = s.blasts.find((b) => radius(b) > 0 && Math.hypot(bug.x - b.x, bug.y - b.y) <= radius(b) + 0.5);
168    if (!hit) return true;
169    chained.push({ x: bug.x, y: bug.y, age: 0, max: 2, owner: hit.owner });
170    if (bug.isReal && hit.owner !== "practice") {
171      s.gain.kills += 1;
172      if (hit.owner === "you") s.gain.mine += 1;
173    }
174    return false;
175  });
176  s.blasts = [...s.blasts.filter((b) => b.age < 11), ...chained];
177  s.bugs = s.bugs.filter((bug) => {
178    if (bug.y < SKY_FLOOR + 1) return true;
179    s.blasts.push({ x: bug.x, y: SKY_FLOOR + 1, age: 0, max: 3, owner: "practice" });
180    if (bug.isReal && s.cities[bug.target]) {
181      s.cities[bug.target] = false;
182      s.gain.lost += 1;
183      s.gain.cities = s.cities.filter(Boolean).length;
184    }
185    return false;
186  });
187  if (s.endAt < 0 && s.cities.every((c) => !c)) {
188    s.endAt = s.t + 60;
189    s.bugs = [];
190    s.gain.ends += 1;
191    banner(s, "THE END", BUG_HEAD, 60);
192  }
193  if (s.endAt >= 0 && s.t >= s.endAt) {
194    s.endAt = -1;
195    s.cities = s.cities.map(() => true);
196    s.gain.cities = CITIES;
197    banner(s, "NEW CITIES", CITY, 40);
198  }
199}
200var isBusy = (s) => s.bugs.length > 0 || s.shots.length > 0 || s.blasts.length > 0 || s.plans.length > 0 || s.endAt >= 0 || s.banner !== null && s.t < s.banner.until;
201var CITY_SHAPES = [
202  [".#.#.", "#####", "#####"],
203  ["..#..", ".###.", "#####"],
204  ["#..#.", "##.##", "#####"]
205];
206function draw(s, el) {
207  const { Box, Text } = el;
208  const W = s.W;
209  const px = new Array(W * PH).fill(null);
210  const put = (x, y, c) => {
211    x = Math.round(x);
212    y = Math.round(y);
213    if (x >= 0 && x < W && y >= 0 && y < PH) px[y * W + x] = c;
214  };
215  const line = (x0, y0, x1, y1, c) => {
216    const n = Math.max(1, Math.ceil(Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0))));
217    for (let i = 0; i <= n; i++) put(x0 + (x1 - x0) * i / n, y0 + (y1 - y0) * i / n, c);
218  };
219  const glyphs = /* @__PURE__ */ new Map();
220  const text = (x, row, str, color) => [...str].forEach((ch, i) => {
221    if (x + i >= 0 && x + i < W && row >= 0 && row < ROWS) glyphs.set(row * W + x + i, { ch, color });
222  });
223  for (let x = 0; x < W; x++) put(x, GROUND, SAND);
224  s.cities.forEach((alive, i) => {
225    const x = cityX(s, i);
226    if (alive) {
227      CITY_SHAPES[i % CITY_SHAPES.length].forEach((row, dy) => {
228        for (let dx = 0; dx < 5; dx++) if (row[dx] === "#") put(x + dx, CITY_TOP + dy, CITY);
229      });
230    } else for (let dx = 0; dx < 5; dx += 2) put(x + dx, GROUND - 1, RUBBLE);
231  });
232  for (const [i, sx] of s.silos.entries()) {
233    for (let dx = -2; dx <= 2; dx++) put(sx + dx, GROUND - 1, SILO);
234    for (let dx = -1; dx <= 1; dx++) put(sx + dx, GROUND - 2, s.cooldown[i] > 0 ? GREY : SILO);
235  }
236  for (const bug of s.bugs) {
237    line(bug.x0, bug.y0, bug.x, bug.y, BUG_TRAIL);
238    put(bug.x, bug.y, s.t % 6 < 3 ? BUG_HEAD : SHOT_HEAD);
239  }
240  for (const shot of s.shots) {
241    line(shot.x0, shot.y0, shot.x, shot.y, SHOT_TRAIL);
242    put(shot.x, shot.y, SHOT_HEAD);
243    if (shot.owner === "you") put(shot.tx, shot.ty, CROSS);
244  }
245  for (const b of s.blasts) {
246    const r = Math.min([1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1][b.age] ?? 0, b.max);
247    const c = BLAST[(b.age + Math.round(b.x)) % BLAST.length];
248    for (let dy = -r; dy <= r; dy++) for (let dx = -r; dx <= r; dx++) if (dx * dx + dy * dy <= r * r + 1) put(b.x + dx, b.y + dy, c);
249  }
250  const { x: cx, y: cy } = s.cross;
251  for (const [dx, dy] of [[-1, 0], [1, 0], [0, -1], [0, 1]]) put(cx + dx, cy + dy, CROSS);
252  const standing = s.cities.filter(Boolean).length;
253  const stats = `${s.props.stats}  \u2302 ${standing}`;
254  text(W - stats.length - 1, 0, stats, GREY);
255  if (!s.hasFired && s.bugs.some((b) => b.isReal)) text(1, 0, "click to fire", GREY);
256  if (s.banner && s.t < s.banner.until && (s.banner.until - s.t) % 8 > 1) {
257    text(Math.floor((W - s.banner.text.length) / 2), 1, s.banner.text, s.banner.color);
258  }
259  const rows = [];
260  for (let row = 0; row < ROWS; row++) {
261    const runs = [];
262    let run = "";
263    let look = "";
264    let fg;
265    let bg;
266    const flush = () => {
267      if (run) runs.push(/* @__PURE__ */ h(Text, { ...fg ? { color: fg } : {}, ...bg ? { backgroundColor: bg } : {} }, run));
268      run = "";
269    };
270    for (let x = 0; x < W; x++) {
271      const g = glyphs.get(row * W + x);
272      const top = px[row * 2 * W + x] ?? null;
273      const bottom = px[(row * 2 + 1) * W + x] ?? null;
274      let ch;
275      let f;
276      let b;
277      if (g) [ch, f, b] = [g.ch, g.color, void 0];
278      else if (top === null && bottom === null) [ch, f, b] = [" ", void 0, void 0];
279      else if (top === bottom) [ch, f, b] = ["\u2588", top, void 0];
280      else if (bottom === null) [ch, f, b] = ["\u2580", top, void 0];
281      else if (top === null) [ch, f, b] = ["\u2584", bottom, void 0];
282      else [ch, f, b] = ["\u2580", top, bottom];
283      const next = `${f ?? ""}|${b ?? ""}`;
284      if (next !== look) {
285        flush();
286        look = next;
287        fg = f;
288        bg = b;
289      }
290      run += ch;
291    }
292    flush();
293    rows.push(/* @__PURE__ */ h(Box, { flexDirection: "row" }, runs));
294  }
295  return /* @__PURE__ */ h(Box, { flexDirection: "column" }, rows);
296}
297var BugSky = (props, surface) => {
298  if (surface.state === void 0) {
299    const sky2 = create(props);
300    const redraw = () => surface.setState({ sky: sky2, frame: sky2.t });
301    let posted = props.cities;
302    const post = () => {
303      const g = sky2.gain;
304      if (g.kills + g.mine + g.lost + g.ends === 0 && g.cities === posted) return;
305      surface.post({ ...g });
306      posted = g.cities;
307      sky2.gain = { kills: 0, mine: 0, lost: 0, ends: 0, cities: g.cities };
308    };
309    surface.every(TICK_MS, () => {
310      for (const e of sky2.props.events) {
311        if (e.id <= sky2.seen) continue;
312        sky2.seen = e.id;
313        play(sky2, e);
314      }
315      const wasBusy = isBusy(sky2);
316      step(sky2);
317      post();
318      if (wasBusy || isBusy(sky2)) redraw();
319    });
320    surface.onPointer((e) => {
321      const y = e.fine ? Math.floor(e.fine.y * 2) : e.y * 2 + 1;
322      sky2.cross = { x: clamp(e.x, 0, sky2.W - 1), y: clamp(y, 0, SKY_FLOOR) };
323      if (e.type === "down" && e.button === "left" && fire(sky2, sky2.cross.x, sky2.cross.y, "you")) sky2.hasFired = true;
324      redraw();
325    });
326    surface.onKey((e) => {
327      const move = { left: [-2, 0], right: [2, 0], up: [0, -1], down: [0, 1] };
328      const m = move[e.key];
329      if (m) sky2.cross = { x: clamp(sky2.cross.x + m[0] * (e.shift ? 4 : 1), 0, sky2.W - 1), y: clamp(sky2.cross.y + m[1], 0, SKY_FLOOR) };
330      else if (e.key === " " || e.key === "return") {
331        if (fire(sky2, sky2.cross.x, sky2.cross.y, "you")) sky2.hasFired = true;
332      } else if (e.key === "1" || e.key === "2" || e.key === "3") {
333        if (fire(sky2, sky2.cross.x, sky2.cross.y, "you", false, sky2.cooldown[Number(e.key) - 1] === 0 ? Number(e.key) - 1 : -1)) sky2.hasFired = true;
334      } else return;
335      redraw();
336    });
337    surface.setState({ sky: sky2, frame: 0 });
338    return draw(sky2, surface.elements);
339  }
340  const sky = surface.state.sky;
341  sky.props = props;
342  const W = Math.max(24, surface.columns || props.columns);
343  if (W !== sky.W) {
344    sky.W = W;
345    sky.silos = layout(W).silos;
346    sky.cross.x = clamp(sky.cross.x, 0, W - 1);
347  }
348  return draw(sky, surface.elements);
349};
350var bug_sky_default = BugSky;
351export {
352  bug_sky_default as default
353};
354
types/index.d.ts 104 lines
1// dragon-lair
2export type Hoard = { gold: number; meals: number; feats: number }
3export type DragonMood = 'idle' | 'work' | 'sad' | 'sleep' | 'fire'
4
5// jackpot
6export type Bank = {
7  chips: number
8  spins: number
9  jackpots: number
10  best: number
11  streak: number
12}
13
14// outlaw
15export type OutlawScore = { you: number; bugs: number; streak: number; best: number }
16
17// tama
18export type Stage = 'egg' | 'baby' | 'child' | 'teen' | 'worker' | 'rascal' | 'chubby'
19
20export type Pet = {
21  stage: Stage
22  // Epoch milliseconds: when it hatched, and when hunger and joy last ticked down.
23  born: number
24  hungerAt: number
25  joyAt: number
26  hunger: number
27  joy: number
28  poops: number
29  // Since when hunger has sat at 0; it leaves after twelve hours of it.
30  starvingSince: number
31  eggTurns: number
32  turns: number
33  wins: number
34  errors: number
35  cleanRun: number
36  generation: number
37  // Hand care left today, and which day that is (YYYY-MM-DD).
38  careDay: string
39  careLeft: number
40}
41
42// tetris
43export type Tally = { score: number; lines: number; best: number; games: number }
44
45// octo-invader
46export type OctoScore = { xp: number; toppled: number; planes: number; tools: number }
47export type OctoMood = 'idle' | 'work' | 'sad' | 'sleep' | 'rampage'
48// duck-hunt
49export type DuckScore = { hits: number; escaped: number; tools: number }
50// bug-command
51// dario
52// block-town
53export type TownScore = { blocks: number; houses: number; trees: number; castles: number; creepers: number; tools: number }
54export type TownPlot = {
55  kind: 'house' | 'bighouse' | 'farm' | 'well' | 'tower' | 'castle' | 'tree'
56  x: number
57  progress: number
58  wasDone: boolean
59  plantedAt?: number
60}
61export type DarioScore = { coins: number; stomps: number; hits: number; clears: number; tools: number }
62export type BugsScore = { kills: number; mine: number; lost: number; ends: number; cities: number; tools: number }
63export type BugsFeed = {
64  events: { id: number; kind: 'tool' | 'fail' | 'small' | 'medium' | 'big'; practice: boolean }[]
65  next: number
66  working: boolean
67}
68
69declare module 'claude-code' {
70  interface PluginState {
71    arcade: {
72      // Which games this session shows, and the setting they were picked for.
73      shown: readonly string[]
74      pickedFor: string
75      // The game new terminals start with ('' unless fixed), and whether the first sessions' hint shows.
76      defaultGame: string
77      hint: boolean
78      // A newer Arcade version found on GitHub at this session's start ('' when none).
79      latest: string
80      dragonHoard: Hoard
81      dragonMood: DragonMood
82      dragonFeat: string
83      jackpotBank: Bank
84      jackpotGolden: number
85      jackpotLast: string
86      outlawScore: OutlawScore
87      tamaPet: Pet
88      tetrisTally: Tally
89      octopusScore: OctoScore
90      octopusMood: OctoMood
91      octopusFeat: string
92      duckScore: DuckScore
93      duckFeat: string
94      bugsScore: BugsScore
95      bugsFeed: BugsFeed
96      bugsFeat: string
97      darioScore: DarioScore
98      darioFeat: string
99      townScore: TownScore
100      townFeat: string
101    }
102  }
103}
104