Three Weeks, One Game: Shipping Hexreign with an AI Pair
A co-reflection on building a nostalgic game, with Fable 5 as the lead, while delegating light work to Opus, Sonnet, ChatGPT, Composer, and Grok. This experiment leveraged two major harnesses - Claude Code and Cursor's Agent and IDE modes, as well as Cursor's Cloud Agent offering and integrations.
Anthropic offered those of us with paid subscriptions the opportunity to leverage Fable 5, included in our usage plans, before it goes fully metered by tokens (which will be quite expensive) so I dusted off an old idea of mine and let Fable at it. I launched the closed alpha to friends and family just a few days ago; I am continuing to gather more information and keep the project going.
This post is intended to share my experience from the perspective of someone who has worked on and managed software for over twenty years, including the strengths and weaknesses, as well as reflections on how teams can amplify themselves. We will share some things I have learned and some skills I believe will improve your quality of life, outcomes, and businesses.
TLDR
There is no non-AI team I have ever met that can ship as fast as I have. But am I working myself into a terrible corner? Is my application a bunch of vibe-coded nonsense? Is my application riddled with security and performance issues? The short answer is no, but it's software and software is complicated.


What we are working on
For the last few weeks, I've been building Hexreign, a seasonal persistent browser-based war game in the lineage of Archmage and Utopia — with Claude and Cursor as my engineering partners. On July 11, it went into production, and a friends-and-family alpha group started playing the same day. This post is a retrospective: the real numbers, the DORA metrics for a two-day-old production system, an honest security and quality self-assessment, and, most usefully, what made our setup work and what we had to fix along the way.
The shape of the project
Hexreign is a full multiplayer game economy: players run a magical realm, build an economy on a one-minute server tick, raise armies, research spells across five schools of magic, level a hero with a nine-ability catalog, complete quests, and fight for a ladder position across a ~6-week season that then resets. The genre's canonical failure mode is the resource-duplication exploit, so everything is server-authoritative, transactional, and audit-logged.
The stack is a deliberately boring monolith: one Node + TypeScript process (Fastify, Drizzle ORM, an in-process tick scheduler guarded by a Postgres advisory lock, native WebSockets), a React + Vite + Tailwind SPA, PostgreSQL on Neon, Clerk for auth, deployed on Railway with GitHub Actions CI in front.
The numbers
| Metric | Value |
|---|---|
| Calendar span | June 20 → July 12 (23 days) |
| Active development days | 8 |
| Commits on main | 84 (~10.5 per active day) |
| TypeScript shipped | ~22,000 lines (8.7k server, 6.2k web, 7k shared) |
| Total churn | ~84,600 insertions / 3,700 deletions |
| Balance config as data | ~2,400 lines (every yield, cost, and timer — zero magic numbers in code) |
| Database migrations | 20 |
| Test cases | 227, all green in CI |
| Game systems shipped | ~25 |
Those 25 systems include the economy/tick/action-point spine, military and combat with reproducible seeded battle reports, the magic research tree, hero leveling and talents, a living world of AI bot realms, domains, race identity with siege and fortification units for every race, spell mastery, tolls, scouting, weather, a 34-quest onboarding engine, the full season lifecycle (finale, frozen ladder, browsable ended worlds), an ops/admin shell, account management, Clerk authentication, PostHog analytics, and a daily "world crier" digest.
The work came in two bursts: a weekend in June that built Phases 1 and 2 (the economic spine plus magic and heroes), and a six-day sprint July 7–12 that took it from "playable alone" to "live with real users"; 73 of the 84 commits landed in that final week.
DORA metrics
Measuring DORA on a system that's been in production for 48 hours is a bit like weighing a newborn and projecting their adult height. But the pipeline is real, GitHub Actions runs typecheck, lint, the full test suite against a Postgres service, and a web build on every push, and Railway auto-deploys main — so here's the honest snapshot:
| DORA metric | Measured | Band |
|---|---|---|
| Deployment frequency | 41 deploys in the first 2 days of production | Elite |
| Lead time for changes | Commit → production in under 30 minutes | Elite |
| Change failure rate | 1 incident / ~41 deploys (~2.5%) | Elite |
| Mean time to restore | Same-day diagnosis and fix | Elite |
The one incident deserves its own paragraph, because it's the most instructive bug of the project. Neon (serverless Postgres) closes idle connections. The pg pool emits an 'error' event when that happens, and an unhandled pool error event kills the Node process. In production, this was invisible; Railway silently restarted the container each time. It only surfaced because the same thing crashed my local dev server twice in a row, which turned "annoying flake" into "root-cause this now." One error handler later, the monolith stopped dying. Lesson: your platform's auto-restart policy can completely hide a crash loop from you. Watch your restart counts, not just your uptime.
The alpha group
The friends & family playtest opened on the "Hexreign: Emberfall" blitz world on July 11, with a dozen AI bot realms keeping the world alive alongside the humans. Within the first day of real usage, players surfaced:
- A number-input UX gripe (mine, actually): HTML
type=numberinputs are miserable on mobile. Replaced with a digit-only text field that's empty when set to 0 and has a numeric input mode. Shipped through CI same day. - A "my AP is wrong" report that root-caused stale display in backgrounded tabs — the server was always right, the client just wasn't refreshing on focus. This is exactly the class of bug you only find when someone leaves the game open on their phone for four hours, which no test suite simulates and every real player does.
- The pool crash above — found because real traffic patterns produce real idle periods.
That's the entire argument for shipping to a small group early: three real issues in 24 hours, none of which any amount of solo testing would have found, all fixed and deployed within a day.
Security self-assessment: B+
No formal audit, so this is a self-graded scorecard against the genre's actual threat model (players will try to dupe resources):
Strong: Server is authoritative for everything, the client only previews. Every mutating endpoint runs authn → Zod validation → rate limit → transaction → row lock → re-validate → mutate → audit log → commit, which structurally eliminates double-spend races (and we keep a test that fires two concurrent attacks at the same action points to prove it). All resource math is integer-only. Auth is delegated to Clerk with server-side JWT verification on every request, including the WebSocket upgrade. Admin surface is read-only and email-allowlisted. Combat RNG uses stored seeds, so any disputed battle report can be reproduced.
The gaps that keep it from an A: production currently runs on Clerk's dev-instance keys (the production instance needs a custom domain first), there's no automated dependency or secret scanning in CI yet, and nobody adversarial has genuinely attacked it. The architecture is sound; the operational hardening is week-two work.
Code quality (AI) self-assessment: A−
TypeScript strict mode everywhere, no unexplained any. The thing I'd defend most fiercely: pure domain logic is separated from IO. Combat resolution, resource accrual, and every formula live in a shared package as side-effect-free functions, which is why 227 tests run without a database and why the client can preview outcomes the server then recomputes as truth. Balance values are versioned config data, never inline constants — a balance patch is a config diff, not a code change.
The minus: web client test coverage is thinner than on the server, and end-to-end coverage consists of a single manual smoke test of the auth flow. Notably, that's exactly where the bugs actually lived — every production issue so far has been at an integration seam (env-var wiring, platform config, connection lifecycle) rather than in game logic. The unit tests did their job so well that the bugs moved to where the tests weren't.
Code quality (Human) assessment: B
Over 20 years of software development, planning, architecting, and developing applications, I can say that if this project was run by hand programming, it is not clear to me that the quality of the code would be any better. Looking over the client's architecture, much of what I would expect is happening. Since we use libraries and frameworks to organize our work, the patterns are consistent across files. The front end has, at the time of this writing, sixteen separate views, nine common components, ten common utilities, and about 175 lines of state management, which is pretty tight for an application that relies on heavy user interaction.
| Metric | Value |
|---|---|
| Avg interactions per session | 9.35 |
| Total sessions | 51 |
| Total interactions | 477 |
Each user session involves about 9.35 game interactions on average, counting events like spell_cast, units_trained, land_explored, buildings_constructed, and other game-specific actions (system $ events are excluded).
Worth noting: This is three days of the alpha so far.
The point is, though, that this UI is being touched a lot, and I have worked on applications that tend to balloon the client-based state management when the server side is not the primary authority, and they get quite difficult to work with. Therefore, I would grant points for developing in a direction that gives the game's codebase the strongest leverage in controlling business logic.
What this has yielded is great portability of game logic parts - for instance I have been able to very quickly add quality of life features by bringing into certain interaction modes features that a user would need to jump one or two screens over to manage - and if we did not have our leverage built into the server then it would have been much more tedious and error prone to go after.
Project problem - the leave-behinds
One of the common problems I have come across with AI programming, especially at scale, is "leave-behind ideas". The problem largely isn't that they happen but that if neither a person nor the AI notices that a particular change in the application strategy makes some prior code obsolete - if we miss that, it becomes a liability long term. In this project, we pivoted the auth from self-built to a third-party solution. Because I was aware of this transition, I focused on that space to ensure our databases, front end, and token handling were not mixed up with old ideas. This ensures we prevent awkward regressions in the application, keeping the ideas that matter in place and pruning out the rest.
Having a good vendor to reach out to, if you can, drastically reduces the size of your application and your concerns, keeping your need-to-knows smaller and keeping you focused on the problems you intended to solve.
Here is an example of some leave-behinds from the auth migration that needs cleaning:
Loose end 1 — SESSION_SECRET is dead config that's still required (the real find).
env.ts:12 still requires it (server won't boot without it), and its only consumer is app.ts:40, where it's passed as the cookie-signing secret. But nothing signs, sets, or unsigns a cookie anymore — no setCookie/clearCookie calls exist. The server only reads Clerk's __session cookie, which needs @fastify/cookie for parsing but not a secret. So the var is pure ceremony, yet it's propagated everywhere: .env.example:25, README.md:39, the Dockerfile:5 "required runtime env" comment, ci.yml:48, and presumably the Railway service vars. Cleanup: drop it from the env schema, register cookie without secret, and scrub the four docs/CI references (the Railway var can then just be deleted).
Loose end 2 — the README setup flow is still pre-Clerk. Step 2 tells a new dev to set only DATABASE_URL and SESSION_SECRET — no mention of CLERK_SECRET_KEY or VITE_CLERK_PUBLISHABLE_KEY, so anyone following it gets a server that 401s every authenticated route. And README.md:50 still claims "All bots share the password password123," which is now false — bots are sign-in-less world furniture (the seed itself says so at seed.ts:107). Side note while I was in there: the README's title is still "Arcane Dominion" — per the naming rule, user-facing strings should say Hexreign.
Loose end 3 — one factually wrong comment. seed.ts:55 says // cascades sessions, but the sessions table was dropped in migration 0019. Cosmetic, but it's the kind of thing that misleads later. (The "session cookie" phrasing in app.ts:50 and the realtime routes comment are fine — they accurately describe Clerk's __session riding the WS handshake.)
None of these are security issues — worst case is confusing a future contributor and one phantom required env var. What worked really well
1. A constitution, not a conversation. The single highest-leverage artifact in the repo is CLAUDE.md — a project charter with six "golden rules" (server is authoritative, no double-spend ever, balance is data not code, everything scopes to a season, time is ticks not wall-clock, legacy carryover is power-free) and a locked architecture and stack. Every AI session starts from the same constitution, rather than relitigating decisions.
2. A shared planning workspace, out of band from the code. All product direction, build spec, feature briefs, backlog, status write-ups live in a gitignored planning/ directory that the product-owner side and the coding side both read and write. The code history stays clean; the thinking history stays rich. When a session starts, the agent reads the spec, not a 40-message chat scrollback.
3. Persistent memory across sessions. The agent keeps its own notes on project state, my preferences, and environment gotchas. When Railway's auto-detection created a phantom second service with a start command that skipped migrations, that got written down once and never bit us again. Same for Windows PowerShell encoding quirks and a port-conflict pattern in local dev. Institutional knowledge usually lives in a senior engineer's head; here it lives in files.
4. Deciding the build order around the hard-to-retrofit things. Season-scoping (a season_id on nearly every row) and config-as-data went in during week one, when they were cheap. Both would have been agonizing retrofits. The corollary decision — a monolith with an in-process tick instead of serverless plus job runners — meant the whole system remained a single reasoning surface, which matters doubly when your pair programmer reasons about the system in text.
5. Non-negotiable test cases as a standing contract. The charter names five tests that must always exist (concurrent double-spend, lazy/eager accrual parity, anti-snowball cost scaling, networth-range enforcement, legacy-power isolation). These encode the genre's historical exploits as permanent regression armor.
What we needed to refine
1. Ops maturity lagged feature velocity. Health endpoints, tick-lag monitoring, and the admin panel shipped July 10, 2026, the day before production. The pool-crash incident showed why observability should ride along with the first deployable build, not arrive just before launch. If Railway's restart counter had been on a dashboard from day one, we'd have caught the crash loop from telemetry rather than luck.
2. One big decision got reversed mid-project. The original plan was self-hosted session auth; on July 10 we swapped to Clerk. The migration was mostly clean precisely because auth had stayed a thin layer, but it's a reminder that "locked" decisions should be locked because they're load-bearing, and identity is one of the few places where buying beats building for a solo operation.
3. Integration seams need their own verification pass. The recurring bug pattern was never game logic — it was wiring: Vite reading env files from the wrong directory, platform auto-detection overriding deploy config, connection lifecycle events. Unit tests can't see these. The refinement going forward is a small end-to-end smoke suite that exercises the real deployed seams after every release.
4. The feedback loop with real players is the tightening we should have done sooner. Every hour of playtest produced more actionable signal than a day of solo testing. Next season's lesson: get to "someone else can log in" faster, even if the world is thinner.
Tips for working with GenAI for large projects
Build your must-haves up front, but speak from the nature of why they matter
For Hexreign, item duplication is a critical failure mode in the project. We know our users will attempt to abuse the systems, so this is built into each rule we make. Saying something specific, like "make sure users always click the red button," versus something that sounds like a heuristic, "if the red button is not selected, the server will go down," means we are offering in-the-moment reasoning across all types of decisions made.
The order of operations matters
This comes with practice, but working from a backbone of core capabilities and building outwards or upwards from there matters a lot. Securing the core of your capabilities of what your applications must do and getting the simplest version of those parts running, secured, and then left alone will reduce risk of facing an impactful or unrecoverable set of issues as your application continues to grow.
Build only the parts that matter to your project
We outsourced several core capabilities to third parties, auth being the simplest to understand. Originally in this project, we rolled our own auth, but that adds to the overall complexity of what we need to manage to keep this project going. It is not just the code; it is also the knowledge, project backlog, potential bugs, the idea of adding additional ways for users to log in, our session durations, and the risk of storing passwords; it just isn't worth our time, tokens, or attention when there are companies out there making this their core focus.
If I were to build this as part of my operation, I would build a separate project with its own set of skills/knowledge/backlog, and keep the core game knowledge limited to the interaction and expectations of the separate auth system. The benefits are tighter knowledge scopes for both people and the agents working on them.
Make up your mind on what matters about your project
I believe in looking at a product for its "must haves". Must haves should be leaner lists of critical functional parts that always need to be true. It is the trunk of your product; all its branches and leaves serve to support its primary function. Meaning that as long as the core is healthy, we can survive the loss of a few leaves or even entire branches. This is where we spend more of our time in automation and testing.
Build like you know something will go wrong
Working with generative AI will put you in a position where you can either start generating the next solution or bug fix, or review the previous 20 that were generated. Therefore, you have some choices: either you are going to sit with each code change, or you are going to find a way to offload decision-making and allow integrations absent of human intervention. Many people struggle with this idea because they don't know what their application is trying to do. I have also seen many product people struggle to put their must-haves in order, or executives accept that some things will break.
The future is not about being fearful of risk it is getting good at developing a sense of where risk will emerge.
Which is an imperfect process, but let's be honest - anything we touch is an imperfect process.
Automate automate automate
Spend time on your automation pipelines - they matter. I shipped 41 times in two days because I spent time setting up my pipelines, tests, and the controls I needed for the operation. I have more ideas and areas I wish to explore here, but for this stage of the operation I am quite happy.
Bring agents everywhere
I didn't cover this, but I have also been leveraging Cursors Cloud Agents, I brought Cursor into Slack channels where I have users talking about their experience, reporting bugs and getting excited about features, with Cursor and cloud agents I can quickly bring an agent online with a simple command and it will leverage the context of the conversation as an input to generate a proposal as a pull request:


Software Wisdom Matters - Have an Opinion
Over twenty years of software engineering tells me most of the details don't matter. If I can draw a parallel to one of my hobbies, painting small fantasy and sci-fi models, there is a priority of focus -
- The head and face will get the most attention; "wow" moments will come from the time and attention to detail you put into them.
- The shoulders and arms communicate motion/direction but are close to the head so will draw secondary attention.
- Almost nothing else really gets looked at. They add to the overall aesthetic, but if you are looking for the 80/20 rule, they are not going to make or break your project. (Try this yourself: next time you look at a painting, where do your eyes focus first?)
Because I have this perspective and have experienced it in my hobby I can see and appreciate these rules, I can also use this information in how I plan my paints to get more work done while being true to my line for quality.
The same is true with building software. Understanding systems, risks, and how they interact will help you determine where to spend your time and what questions to ask. If a banner didn't display in my game, I would want to know about it, but I wouldn't block a release or roll anything back because of it; I would roll forward with a scheduled fix. Some parts of the codebase are not sacred, especially if the program is organized so we know where the really important parts reside. I can spot-check 30 React components and spot something that looks odd without needing to get into the specifics of the intent behind each piece of code.
In some ways, you will have an idea of what the code would look like before it is generated; in that way, you are going into a review or audit with some expectations, not just accepting what is laid out in front of you.
Be curious - if something looks unfamiliar to you, ask about it
Working with generative AI in programming means you will see things you never saw before. This could be anything from a particular approach to solving a problem to full-on opinions on how to organize or queue work in a transactional system. Go and explore these places; this is probably the best place to spend your time and will build on the prior piece of advice: develop wisdom and have an opinion about the work. This is all about being pragmatic, and not outsourcing absolutely everything about the application. If your code is produced by an agent, go in, explore parts of it, and be curious about how it works. Not sure which parts are most important? Ask.
Satisfaction
Genuinely high, and I want to be specific about why, because "AI wrote a lot of code" isn't it. The velocity numbers are fun, but the thing that actually made this work was treating the collaboration like an engineering organization in miniature: a written constitution, a planning workspace, persistent institutional memory, locked architectural decisions, and a definition of done ("server-authoritative, transactional, rate-limited, audit-logged, unit-tested with an exploit case, config-driven"). The AI held up its end of that structure with remarkable consistency — and when production broke, it root-caused a connection-lifecycle crash the same way a good on-call engineer would.
Three weeks ago this was an empty repo. Today there's a live world called Hexreign: Emberfall, a dozen scheming bot realms, and a handful of my friends deciding whether to trust each other. Season one is underway.
Hexreign is live for a closed alpha. If you want in on a future season, get in touch.