npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hopple

v0.1.0-rc.14

Published

The Hopple command line: create a game, run it in the dev shell, check it and publish it.

Readme

hopple: the command line

The command-line tool for game developers:

  • hopple create starts a new game with the recommended setup (optional).
  • hopple dev runs a built game in the dev shell, a host made for testing, which runs it with the same code as the real clients.
  • hopple smoke plays a built game headless, at every screen shape and device tier, and says what a player would trip over.
  • hopple check says whether a game can be published. hopple upload uploads it to a Hopple server, for a link to try it, and hopple publish uploads it and asks for it to be published.
  • hopple versions, withdraw, untag, unpublish, rollback and listing say and change where a game's versions stand.
  • hopple list, login, logout and whoami manage what you've uploaded and who you upload as.
  • hopple collaborators chooses who else may publish a game.

It is a dev dependency of the game (npm install --save-dev hopple, which hopple create sets up), run with npx hopple. It is one compiled file with no dependencies, and needs Node 22.18 or later.

hopple is not a bundler. It takes a game that is already built, by whatever tool the game uses.

The project's config

The commands about a game (dev, check, upload, publish, versions, withdraw, untag, unpublish, rollback, listing, collaborators) work on a project: a directory with a hopple config in it. They find it in the current directory, or the nearest one above it (like tsconfig.json). The config says everything they need to know:

// hopple.config.ts
import type { HoppleConfig } from 'hopple';

export default {
  build: 'npm run build',
  watch: 'npm run watch',
  page: 'dist/index.html',
  manifest: {
    id: 'my-game',
    version: '0.1.0',
    title: 'My Game',
    orientation: 'portrait',
    audio: null,
  },
} satisfies HoppleConfig;
  • build (optional) is the command that builds the game. It runs first, in the config's directory, and a failed build stops the command. So npx hopple publish can't upload a stale build. --no-build skips it.
  • watch (optional) is the command that rebuilds the game on every change and keeps running. hopple dev runs it, after the build, until it stops.
  • page is the built HTML file, relative to the config: the index.html of the build's output directory, which is played in the dev shell and published whole. A page beside the config has no output directory, and is published alone.
  • manifest is what the game is: its id, title, description, and what it declares. See the manifest's reference. Its api may be left out: every command fills it from the installed @hopple/sdk (1.6.2 is "1.6", a pre-release is "next"), and refuses a manifest whose api differs from it. The manifest is checked with the validator the installed @hopple/sdk carries, which is of that API, so a CLI newer or older than the SDK judges it the same; with no SDK installed, with the CLI's own. Likewise previews.network: a project depending on @hopple/sdk-preview-network (its package.json lists it) declares the network preview at the installed revision (0.1.4 is 1) without saying so, and a manifest naming another revision than the one installed, or naming one with none installed, is refused. A @hopple/sdk-preview-network merely found above the project, as another game in a workspace installs it, is checked against but declares nothing.

Only the page and the manifest are uploaded; build and page never leave your machine.

The config can be hopple.config.ts, hopple.config.js (both default-export it) or hopple.config.json (which is it). Its type, HoppleConfig, is this package's: import type { HoppleConfig } from 'hopple', whose manifest is the type the installed @hopple/sdk gives it. A TypeScript config needs no build of its own: Node runs it. Any other key is an error, so a misspelt build can't be silently skipped, and two configs in one directory are an error too.

A JSON config can start with "$schema": "./node_modules/hopple/config.schema.json" for completion and errors in an editor, the manifest's included.

Commands

hopple create  <dir>
hopple dev     [project] [--port <n>] [--server <url>] [--no-audio] [--no-build]
hopple check   [project] [--server <url>] [--no-build]
hopple upload  [project] [--anonymous] [--server <url>] [--no-build]
hopple smoke   [project] [--shapes <list>] [--script <file>] [--cover] [--sound | --no-audio] [--out <dir>] [--seed <n>] [--server <url>] [--no-build]
hopple publish [project] [@<build>] [--beta] [--anonymous] [--no-live-check] [--server <url>] [--no-build]
hopple versions [project] [--server <url>]
hopple withdraw [project] [--beta] [--server <url>]
hopple untag   beta [project] [--server <url>]
hopple unpublish [project] [--server <url>]
hopple rollback [project] [--server <url>]
hopple listing [project] [listed | unlisted] [--server <url>]
hopple list    [--server <url>]
hopple share   <link | id> [--data] [--json] [--server <url>]
hopple collaborators [add <handle> | remove <handle>] [project] [--server <url>]
hopple login   [--server <url>]
hopple logout  [--server <url>]
hopple whoami  [--server <url>]
hopple --version

[project] is a project's directory (or a directory inside it), or its config file. Left out, it is the current directory.

The server is taken from --server, then $HOPPLE_SERVER, then https://hopple.app. It is named by its site; the CLI finds the API under /api on it.

Out of date

create, dev, check, upload and publish end by saying when what the agent works from is out of date:

  • The Hopple skill. Each copy of the hopple-game skill in an agent's skills (~/.agents/skills, ~/.claude/skills, $CLAUDE_CONFIG_DIR/skills, and .agents/skills and .claude/skills in the project and the directories above it) whose Skill version: is older than the one https://docs.hopple.app/skill.md serves is named, with the command that updates it: npx skills update hopple-game -g -y for one the skills installer put there for the user (npx skills add https://docs.hopple.app -g), -p -y in the project for one it put in a project, and for a copy made by hand, to install or read the current one.
  • This CLI. create, dev and check ask the server whether it would refuse this CLI, which it does below the version it supports, and say what to update; upload and publish are told by the server itself.

It never fails or slows a command: it runs beside it, each request gives up after 1.5 seconds, and what it learns is kept in updates.json beside the sessions file (the site's skill version for 6 hours, a server's answer for an hour). With no skill installed it asks the site nothing, and offline it says nothing. HOPPLE_NO_UPDATE_CHECK=1 turns it off; $HOPPLE_DOCS points it at another documentation site.

hopple create

npx hopple create my-game     # or --next, for @hopple/sdk@next
cd my-game && npm install
npx hopple dev        # build, then play it in the dev shell; it rebuilds on every change
npx hopple upload     # build, check, then upload: a link to try it
npx hopple publish    # build, check, then publish

Writes a new project into <dir>, which must be new or empty. The directory's name becomes the game's id (lowercase letters, digits and hyphens). It is the recommended setup, not a requirement:

  • @hopple/sdk is the one npm's latest names now (^1.6.0), or with --next the one next names, exactly (2.0.0-next.3): next is the API's next release, which breaks whenever it needs to, so the game stays on it until it is moved on by hand. Only accounts an admin allows may publish on next. create asks the registry npm_config_registry names, npm's own otherwise.

  • Vite builds it into dist/, with base: './' so every address in it is relative, and dist/index.html as its page.

  • index.html loads the runtime as its first script, <script src="_rt/hopple.js"></script>. The runtime is the SDK, served beside the game by whatever runs it; the @hopple/sdk package the game imports is its types and a stub that forwards to it. So a fix to the SDK reaches the game without a rebuild. Keep the tag first.

  • hopple.config.ts builds with npm run build (typecheck with tsc, then bundle), rebuilds with npm run watch (vite build --watch), names dist/index.html as the page, and holds the manifest.

  • vite.config.ts turns HOPPLE_DEV into import.meta.env.HOPPLE_DEV, and marks the page of a build for hopple dev (see below). Being TypeScript, a mistake in the manifest is a type error.

  • There is no publish script: npm publish would run it, then upload the project to the npm registry. The project is also "private": true.

  • src/game.ts is a small working game (a ball you keep in the air) that uses the SDK the way every game should: init, the loop, layout, input and pause.

A game built any other way (another bundler, or a hand-written HTML file) is played and published with the same commands, once it has a config.

hopple dev

npx hopple dev                          # this project's game, built: http://localhost:5173
npx hopple dev path/to/project --port 8080

The dev server serves:

  • /: the dev shell.
  • /game/: the config's page, and /game/… the other files in its directory, with caching disabled. Nothing outside that directory is served.
  • /game/_rt/hopple.js: the runtime, beside the game as it is beside every published version. It is the release of the game's label (the track, the major its manifest's api names or next, then each preview it declares at its revision, next+network.1) the server's players get, fetched from the server (--server, as for the other commands) when hopple dev starts, or this CLI's own build of that label when the server can't be reached or answers with another; the terminal says which. A server's release can be newer than this CLI's shell understands, and the shell then says to update hopple.

A valid manifest is shown in the shell. An invalid or missing one is reported in the terminal and on the page, naming the bad fields, and the game still runs. The config is read again every time the shell page loads, so an edited manifest shows up on reload, with no restart. So does a rebuilt game.

It plays a build that loads files from beside its page (a separate script, a stylesheet, images), but says in the terminal that it can't be published like that.

On a phone. The dev server listens on this machine alone. Open on a phone, in the shell's Device panel, opens it to this machine's network too: the shell on one port at this machine's address and the game on another, so the game keeps an origin of its own, and a QR code of the shell's address there, carrying the panel's settings (all but the shape, the host chrome and the notch, which the phone has of its own). Scan it on a phone on the same Wi-Fi. Everything the dev server serves, the relay included, is open to the network from then until stop network access, or until the server stops; only the shell on this machine can turn it on or off (/network, from its own origin, with a header no form can send). The terminal says when it is on.

Without sound. --no-audio, or HOPPLE_NO_AUDIO=1 in the environment, starts the shell muted, the way a player's mute does: the game still runs its sound and obeys audio.allowed, and is told it is muted, so its sound is exercised and makes none. The pill over the game shows it muted, and a person can unmute it. The developer's own mute setting, kept for the next page, is left as it was. Set HOPPLE_NO_AUDIO=1 once for an agent or a CI machine: hopple smoke and the check hopple publish makes are silent anyway, and --sound does not turn hopple smoke's sound on while it is set.

Developer-only builds

hopple dev runs the config's build, then its watch, with HOPPLE_DEV=1 in their environment. Every other command runs build with HOPPLE_DEV taken out, even when the terminal has it. So a game can build in what only its developers should have, such as cheats or a level select, and leave it out of everything else: in a project hopple create made,

const dev = import.meta.env.HOPPLE_DEV ? await import('./dev.ts') : null;

is null in a published build, which doesn't contain dev.ts. A runtime test (the address, the host) would publish what it guards.

A page built this way carries <meta name="hopple-dev">, which check, upload and publish refuse: a page built by hopple dev and uploaded with --no-build, or rebuilt by its watch while they run, is never published.

watch runs in a process group of its own, stopped with hopple dev. When it stops by itself, hopple dev says so and keeps serving the last build.

With no project to be found, the shell starts empty and waits for a URL like /?server=<server url>&game=<owner>/<name>@<build> (or @beta). The dev shell link hopple-admin review prints for each waiting version is exactly that, and so can a link you write for any uploaded game (yours or someone else's). The shell downloads the game and its manifest from that server's feed and runs it exactly as it would a local build.

Playing with others

hopple dev runs Hopple's own relay, at /net on its port, so a game declaring the network preview gets parties and matches on the same rules as on the site, with no server and no account. The sheet has the site's Party tab.

A networked game opens on two players tiled in one window, each a whole dev shell in a frame of its own, already in one party:

  • Tiling works as in an IDE. Drag a player's title bar onto the edge of another to split that pane there, or onto its middle to swap the two; drag a divider to resize; double-click a title bar (or ⤢) to play that player full size. The bar above has side by side, stacked and grid, and add a player, up to 8. The layout is kept for the game.
  • The focused player (the one clicked last, outlined) is the only one heard and the one the keyboard reaches. A real pad goes to its player: the first pad to Player 1, the second to Player 2.
  • Each player keeps their own id, save and connection, as a separate device would. A pane's × takes that player out of the party and the match.
  • /?player=N opens one player alone, at full size. A party's or a match's link opens one shell as a guest, which is how a phone on the same network joins.

The Dev tab's Network part, for each player:

  • Network: none, lan, wired, wifi, mobile, weak or far, the delays, jitter, loss, stalls and bandwidth of @hopple/netlab's profiles, put between the player and the relay.
  • Faults: drop the connection (it comes back on its own), stall it for 3 s, take it offline for 35 s (past the 30 s the relay holds for a player coming back), switch away for 5 s, or reload.
  • Match: its code, seats, lock, kept messages, and its people with who hosts it and who is away or gone.
  • Traffic: what the game sent and heard, against the relay's limits (a message at most 4096 bytes, 60 a second in bursts of 120), with a warning when it comes near them and every refusal counted, and the latest messages each way.

The shell a server hosts as plain files, with no hopple dev behind it, has no relay and offers no matches.

hopple check

npx hopple check                  # this project's game, built
npx hopple check --no-build       # as it was last built

Everything publish checks before it uploads, without uploading:

  1. The manifest validates.
  2. The page exists and is its output directory's index.html, and loads nothing the directory doesn't hold: every <script src>, stylesheet <link>, <img> and CSS url() is a data: URL or a file in the directory, referred to relatively. That directory is what is published, at the root of the version's own address, and a game reaches nothing else. (Files the game's code fetches can't be seen this way; they fail the same way.) A page beside its config is published alone, so it must contain the whole game. The runtime, _rt/, is not the game's: it is served beside every game, and the directory may hold nothing there.
  3. The page loads the runtime as its first script, <script src="_rt/hopple.js"></script>, as it is, and no page or script carries a copy of the SDK.
  4. Nothing is addressed from the root (/assets/x.png names the root of whatever serves the game, not the game), no page has a <base>, and every page is UTF-8.
  5. No script uses localStorage, sessionStorage, IndexedDB, Cache Storage or document.cookie: a game's origin keeps nothing past a reload, and the runtime makes them throw. Use save instead.
  6. The runtime the server serves the game's label is not older than the installed @hopple/sdk: a game may use anything its SDK describes, so on an older runtime it can fail as it starts. On next every pre-release counts; on a major, the minor. Asked of the server (--server, else $HOPPLE_SERVER, else hopple.app), and skipped when it can't be reached. hopple dev warns of the same.

The server checks the same, in the same words. The CLI also refuses a page built for hopple dev (see Developer-only builds).

It exits with 1 and says what is wrong, or prints the game's id, version and size. Over 2 MB compressed it warns that a player on a phone waits for the game, and over 10 MB publish refuses it.

hopple smoke

npx hopple smoke                              # every shape the game is played at
npx hopple smoke --shapes desktop,low-tier    # some of them
npx hopple smoke --script smoke.json          # with scripted input
npx hopple smoke --cover                      # and a cover

Plays the built game headless, in the real runtime and host, in the Chrome or Chromium this machine has ($HOPPLE_CHROME names one). The runtime is the release players get, as hopple dev serves it, and drives the game's clock: time moves only when the run steps it, a frame at a time, Math.random is seeded (--seed, 1 unless given), and every press lands on a frame fixed in advance, so two runs of one build do the same.

Shapes. A phone held each way the manifest's orientation allows (phone-portrait, 390 × 844, and phone-landscape), a desktop (1280 × 800), and low-tier, a phone the way the game is held at the low device tier with its CPU slowed four times; tablet (1024 × 768) when named. Phones are touch, with the SDK's on-screen controls; the desktop has a keyboard and mouse. Each is laid out as the site lays it out: the rail and a phone's notch are reserved out of the frame, or told to a game that draws under them.

What a run does. It waits up to ten seconds of the game's time for it to say it is ready, looks at its title, taps or clicks the middle and presses Enter and Space, then for eight seconds presses each declared action's first key in turn and, on a touch screen, taps around it. --script <file> replaces all of that after the boot with a JSON list of steps: {"wait": frames}, {"press": "<action or key code>", "frames": n}, {"tap": [x, y], "frames": n}, {"drag": [[x, y], [x, y]], "frames": n} and {"shot": "<name>"}, x and y being fractions of the frame from 0 to 1.

What fails it, each said with the shape it happened at:

  • the game throws, or never says it is ready;
  • in what the page draws as HTML, at each shot: text under 10 CSS pixels, cut off by its box, or past the screen's edge where nothing scrolls it into view; text the colour of what is behind it; a control off the screen, covered by something a tap lands on instead (the SDK's own on-screen controls included), or under the host's rail or the notch;
  • on a desktop, the game drawing in a phone-shaped box less than 60% of the screen wide;
  • at the low tier, half the frames taking over 33 ms (under 30 frames a second): its own work, and the drawing it left the canvas to finish.

It warns of text under 12 pixels or of low contrast, a picture that never changes through the eight seconds of input, a game paused at the end, and analytics events the manifest declares that no run sent. Text a canvas draws can't be read, so a person looks at the pictures.

What it writes, into .hopple/smoke/ beside the config (--out for elsewhere): sheet.png, every shape's key moments side by side, for a person to look over in one go; each moment's picture; and report.json, with the problems and warnings and, for each run, what the game sent as analytics, saved, scored and unlocked, and how long its frames took, for a test to check. --cover also plays it at 1200 × 630 and writes the last picture, or the one a script's {"shot": "cover"} takes, where the manifest's cover names, or as cover.png beside the config.

It makes no sound: Chrome is muted and the host tells the game it is muted. --sound turns it on, unless $HOPPLE_NO_AUDIO=1. It exits with 1 when anything failed.

hopple upload and hopple publish

npx hopple upload                 # this project's game, built: uploaded, not published
npx hopple publish @3             # publish build 3, the one the upload printed
npx hopple publish                # build, upload, and publish in one go
npx hopple publish --beta         # the same, as the beta
npx hopple publish path/to/project

upload:

  1. Makes every check hopple check makes, so mistakes are caught before anything is uploaded.
  2. Says which account it uploads as, or that it uploads anonymously, before it sends anything. An anonymous session is refused a project an account published to that server from here before, or whose manifest names an owner: its upload would be a separate anonymous copy of the game, and the account's would keep its old build. It says to sign in as that account; --anonymous uploads the copy anyway. Who published a project is kept beside its config, in .hopple/published.json, by server, after every upload from an account.
  3. Uploads the manifest and the page as <owner>/<manifest id>, naming the installed @hopple/sdk's version: the server refuses a version built against an SDK newer than the runtime its label has out, until that runtime is released. The owner is the manifest's owner, or you when it names none; only the owner and the game's collaborators may upload it. The game's title and description are the manifest's title and description: to change them, change the manifest.
  4. Prints the version, as the manifest's version and the build the server numbered it (owner/name 1.5.2 (build 3)), a link that opens that exact version, and how to publish it (hopple publish @3).

The version is not published: nobody browsing the site finds it, and only people given the link can play it. Until an admin approves it, it plays without scores, saves or achievements. Files and a manifest the game already has are that version again, not a new one: uploading files already uploaded prints the build they are.

publish uploads the same way, then asks for latest to point at the version: latest is what the game's address opens, and what the feed shows while the game is listed. With --beta it asks for beta: a newer version that players who opted in get, while it is newer than latest, with its own boards and achievements. A beta no newer than latest is refused, and publishing latest at the beta or past it ends the beta. With @<build> it builds and uploads nothing, and asks for that build, already uploaded. A publish after an upload of the same files asks for the uploaded version, since the upload is that version again.

A published version names one build: a version whose manifest version is already a published build's is refused, and its upload warns about it. Raise version and upload again.

  • Once a tag moves to the version, publish loads it once, headless, from its own address with the runtime that address serves, as hopple smoke does, and says whether it starts and throws nothing. It exits with 1 when it does not, so whoever published it hears about it, and roll it back with hopple rollback. With no Chrome here it says nothing; --no-live-check skips it.
  • A trusted user's or an admin's request moves the tag at once, whoever owns the game, and so does a request for a version already approved: that is a rollback (hopple publish @2).
  • Anybody else's waits for an admin to approve the version, while the tag stays where it was. Approving it moves the tag. A game has one request waiting per tag: a new one replaces it, and the version it named stays, uploaded. You can have at most 5 waiting, across games.

It prints whether the tag moved or the request waits for review, and a link to the version. hopple withdraw takes back a request still waiting.

Publishing needs an account. Without hopple login, publish uploads as upload does, asking for nothing, and says to sign in, then hopple publish @<build>; publish @<build> uploads nothing, says to sign in and fails. upload uploads, says it was anonymous, and says to sign in before publishing. Sign in before or after: the games uploaded anonymously from this machine move into your account.

hopple versions, withdraw, untag, unpublish, rollback, listing

npx hopple versions               # every version: its review, its tags, what waits
npx hopple withdraw               # take back the request waiting for latest
npx hopple withdraw --beta        # or for beta
npx hopple untag beta             # end the beta
npx hopple unpublish              # take the game down: no latest, no beta, nothing waiting
npx hopple rollback               # latest back to the approved version before it
npx hopple listing unlisted       # out of the feed and search; listed puts it back

They work on the game publish would upload to, for its owner, its collaborators and admins.

  • versions lists every version, newest first: whether it is approved, turned down (with the admin's note) or not reviewed, the tags pointing at it, the tag it waits for, who uploaded it and when, and its link. It says whether the game is listed.
  • withdraw closes the request waiting for latest, or with --beta for beta. The version stays, and its link still opens it.
  • untag beta ends the beta: its players get latest.
  • unpublish takes the game down: it removes latest and beta and withdraws what waits for review, so nothing brings it back until somebody publishes it again. Its versions stay, and their links still open them.
  • rollback points latest at the newest approved version before the one it points at; run again, it goes further back. After an unpublish it points latest back at the version it had. With nothing to go back to, it fails.
  • listing says whether the game is listed: in the feed and search while it has a latest. listed or unlisted sets it, which only the owner may do. An unlisted game keeps its tags, its scores and its links.

hopple login, logout, whoami, list

  • The first command that talks to a server starts an anonymous session, named anon- and three words, so you can upload without an account.
  • hopple login signs that same session in. It prints a link and a short code, and waits: open the link in a browser where you are signed in to the site (or sign in there, any way the site offers), check that the page shows the same code, and approve. Nothing is typed in the terminal, and the link can be opened on another machine. It is good for ten minutes. Anything you uploaded anonymously from this machine moves into your account. A sign-in lasts 90 days from when it was last used. Once one ends (expired, or gone from the server), commands stop with an error until you run hopple login again; they do not quietly carry on anonymously.
  • hopple whoami shows who you are on that server and how you signed in.
  • hopple logout ends the session and forgets it.
  • hopple list shows each game you own or collaborate on, whether it is listed, the versions its tags point at, those waiting for review, its newest version, and a link to each.

Sessions are stored per server in ~/.config/hopple/sessions.json (or under $XDG_CONFIG_HOME, or $HOPPLE_CONFIG_DIR if set), readable only by you. The file contains your session tokens, so treat it like a password.

Every request names the CLI and its version (User-Agent: hopple-cli/<version>). A server refuses a CLI older than it accepts, saying to update it. It also uses the version to count games published and terminals signed in from the CLI, under your Hopple player in our usage analytics. Nothing else is collected, and nothing is collected on your machine. Set DO_NOT_TRACK=1 to leave your publishes and sign-ins out of those counts.

hopple collaborators

npx hopple collaborators              # who else may publish this project's game
npx hopple collaborators add ada      # let ada publish it too
npx hopple collaborators remove ada

A collaborator may upload new versions of the game, ask for its tags, withdraw its requests and end its beta. Nothing else: listing it, deleting it and choosing who publishes it stay with its owner. The game is the one publish would upload to, <owner>/<manifest id>. Only accounts can be added, and each is told in their inbox.

To publish a game you collaborate on, set the manifest's owner to the owner's handle. Without it, publish makes a game of your own instead. Your requests move its tags or wait for review by your own trust, not the owner's.

hopple share

npx hopple share 'https://hopple.app/ada/pocket?share=…'   # the record behind a link
npx hopple share <link> --data > state.txt                 # the game's data alone, byte for byte
npx hopple share <link> --json

The record behind a link a player shared: the game's data and meta, when it was made and on which build of the game, where it was made and how it left the page, and how many people opened it. It takes the link as the player sent it, or its id. To get debug data from a player, have the game put it in a link (hopple.share.offer({ data, meta })) and ask them to send you the link. Data a game handed over as bytes (a Uint8Array) is written by --data as those bytes, and shown in base64 otherwise.

For the game's owner, collaborators and admins. Reading a record does not count as opening the link. Links made in the dev shell are kept in its browser, not on a server, and are read in its log.

The dev shell

The dev shell runs the game the way real hosts do (same sandbox, same bridge), but surrounds it with controls for testing. Its panel (opened from the rail's handle or the corner button) has:

  • Device: the frame's shape (portrait phone, landscape phone, or the whole browser window), where the host's buttons go, and a fake notch.
  • Lifecycle: switch the game between active, passive and hidden; the log shows the orders each one sends (sound, loop, resources). Ping it.
  • Info: device tier, locale, whether the player is returning, and "be a new player". Each of these restarts the game, because info is only sent once, at the start.
  • Manifest: what the game declares.
  • The log: every analytics event, every refusal, every save read, write and clear (with sizes), every share link, and every error the game threw, with its stack. A new error is also said over the game in a notice that goes away on its own, and is printed in the terminal hopple dev runs in: once with its stack, then again only when it has been thrown 10, 100, 1000… times, and whole again after a restart.

Switches that make the host misbehave on purpose, so you can see your game cope:

  • Reserve the host's buttons even though the game said draw-under.
  • A host with no save store, a full one, or one that takes 2 seconds to reply.
  • A host with no scores, no achievements, no panel, no input settings, no audio settings, or that never asks the game to pause. These restart the game, since what a host offers is settled at the start.
  • Analytics rate limits like a real host's: 10 events a second, 15 in 15 seconds, 30 a minute.

What each feature does in the shell:

| Feature | In the dev shell | |---|---| | Save | Stored in the browser's localStorage. The panel shows what is stored, its size and age, and can delete it. | | Score | A made-up leaderboard, so you can see how a rank looks before any real players exist. You can set the range. Your submitted scores join it. Reloading generates a new one. | | Achievements | Kept in memory for one player. Reset clears them without restarting the game (the running game keeps what it was told, just as in a real client). | | Share | Every link (the game's, or the rail's Share button) gets a record kept in the browser's localStorage: where it came from, and the game's data and meta. The record is written to the log, the link is copied to the clipboard, and the game is told copied. Opening the link in the same browser restarts the game with that data. Open without the link drops it. | | Panel | The same panel as a real host's, with the game's pages in it. The game can ask for one; opening the panel yourself, on the game's pages or on others, shows how the game reacts to a panel it didn't ask for. | | Input | The key-binding page, a switch for which input device the player is using, and a virtual pad to play the game with. | | Sound | Mute, master volume and a slider per declared channel, in the panel, on the game's page. The log says whether the game answered with the mix it was asked for. | | Pause | A button in the corner, shown only when the game says it can pause. |

License

MIT; see LICENSE. The files hopple create writes into a new project are under 0BSD instead, so a game made from them owes nothing to anyone. The license covers the code, not the Hopple name or logo.