All work

2026 · Design, engine, art

To Arms, Squire!

A browser RPG built from scratch: card-driven duels, bosses that learn your habits, a pixel world stored as text, and a simulator that keeps it fair.

  • TypeScript
  • Next.js
  • React
  • Zustand
  • Supabase

To Arms, Squire! is a role-playing game that runs in the browser, and everything in it is built from scratch: the combat engine, the boss AI, the world, the pixel art, and the tools I make them with. You play a squire whose knight rode out and never came back. You set out to earn your own name, and the road ends at the old seals under the realm. The longer you take, the harder the world gets, and when a run ends, it's shuffled and dealt again.

It's playable right here. There's no sign-up: the game saves to an anonymous session as you play, and adding an email later keeps that save on any device.

A duel mid-bout. My three cards are on the table; the foe's are still face down.

The duel

Fights are played with cards. Each bout you lay three action cards on the table, in order: attack, block, dodge, catch your breath, or one of the stronger techniques your build has earned. The foe commits three of its own, face down, though a boss shows you its first. Then the table turns over one round at a time and each pair of cards resolves against the other.

Three bars run every fight. Health is your margin for mistakes. Stamina pays for your cards, and catching your breath buys it back; if you can't pay, you lose the round and stand open. Posture soaks up pressure, and when it hits zero your stance breaks and you lose the next round instead. Most of the game's decisions are about which of the three you're willing to spend.

The cards are built to be read like playing cards. The price sits in the corner, the payoff is the one big number in the middle (the damage it will do, the guard it raises, your odds to dodge this foe's swing), and the small print lists the odds and whatever else wakes up when you play it. Rules text names a status once, and a single glossary puts its definition on the word as a hover, wherever it appears.

Bosses that read you back

Common foes draw from a weighted hand of moves, and elites play the solver described below. Bosses and minibosses get an AI that works in three steps, every bout:

  1. Nominate. The boss's script proposes a few lines that are in character. The Graven Warden holds his wall; in his second phase he parries if you've been swinging at him, and presses if you haven't.
  2. Price. A simulator runs each line through the real combat resolver against a model of what you tend to play, and drops any that price far below the best.
  3. Pick. A weighted shuffle chooses among what's left, so the boss is never fully predictable.

Personality lives in the script, competence in the pricing, texture in the noise. The AI only ever reacts to what has been revealed. It never peeks at your face-down cards, which is what lets the game show you a boss's opening move before you commit. It can change its later cards mid-bout, but only in answer to cards already turned over. A boss fight should be something you learn, and learning needs honest tells.

A world stored as text

Every sprite in the game is data: a small grid of characters in a TypeScript file. A dot is a clear pixel, o is the outline, and the letters A to H are roles that each sprite points at a palette colour, like metal or wood. Re-colouring is a one-line change, a sprite shows up in a code diff like any other change, and one shared renderer turns any of them into crisp SVG. There are four libraries: the 12×12 interface icons, the characters, the tarot art, and this site's own sprites, the tree on the home page included.

The figures below aren't a screenshot. They're drawn live from those grids:

The squire
Graven Warden
Adrix
Brother Ilm
Dark Beast
Ulgar
The squire and the five main bosses, rendered from the game's sprite data at a whole-number scale. The bosses are mirrored to face you, the way the duel stage draws them.

I draw and edit most of them in a sprite editor I built for the job, which saves straight back into those files.

Keeping it fair

A game like this is a pile of numbers that all touch each other, so I don't tune it by feel alone. A headless simulator plays the real combat engine with scripted players: thirteen kinds of build, from heavy hitters to casters, each played twice, once with ideal gear and once with the gear a real run tends to find. One pass is thousands of fights against the five main bosses. The target is for the median build to win between 20 and 40 percent of the time: hard, but a fight you can learn.

Alongside it, an integrity suite holds the content to its own rules. Every tarot card is accounted for, the price a dialog quotes is the price you pay, no description hard-codes a number its effect owns, and the text a player sees is checked for internal terms. It runs in about ten seconds, so it runs all the time.

tarot cards
78
simulated builds
26
automated checks
600+

The arena

When a run ends, its knight can stand in the arena as a champion. Other players duel it, and the champion is played by the same boss AI, which has already studied the challenger's habits. The server hands out the random seed along with the opponent and records a result only after replaying the whole fight itself from that seed, so every recorded result is a fight the rules really produced. Ratings use Glicko-2, so a new champion's rating settles as it fights.

What's next

Each of the tarot deck's twenty-two major arcana plays by a rule no other card shares, and each now wears its own illustration. A harder ladder for players who have won a run is built and waiting on my own playtest before it opens.