I've spent the last few months building Galactic Idle, a space-empire incremental game, using Vue 3, TypeScript, Capacitor (for Android), and an AI pair programmer. This isn't a "AI will replace us" or "AI is overhyped" post. It's a field report on where the time actually went.
Short version: the AI collapsed the cost of writing code. The hard parts moved somewhere else.
The stack
- Vue 3 + TypeScript + Vite for the game and UI
- Capacitor to ship the same code as an Android app
- Vitest + Playwright for unit and e2e tests
- PostHog for opt-in, consent-gated analytics
- Nine locales, with a coverage check so missing translation keys fail loudly
All game state lives in one big composable, useGameStore.ts. That is not a recommendation. It's a confession, and it's why I'll come back to cleanup in a minute.
What the AI made cheap
Things that used to cost me days and now cost me an afternoon:
- Wiring a consent-gated analytics layer that forwards from a central
logEventfunction - i18n across nine languages
- A trailer capture rig: a Playwright script that plays the game with an injected cursor ghost and a virtual clock, then renders both 16:9 and 9:16 gameplay trailers. I'll probably write that one up separately.
- Refactors that touch dozens of call sites, like replacing scattered
toast()calls with a structuredlogEvent/grantResourcesspine and result cards
If your bottleneck was "I know what I want, I just have to type it," you'll feel this immediately.
What it did not make cheap
1. Balance is a data problem, not a code problem
Idle games are economies. Early on, tapping was too strong, so I capped clickPower (5000 → 900) and sqrt-dampened the upgrade stack. Then conquest rewards were too generous, so I cut the ×3/×5 global reward upgrades. The AI implemented each change in minutes. Knowing which change to make required numbers.
So I gave myself a way to get them. The store exposes a hook that only exists when the URL contains ?__sim:
// Balance-sim hook — only active when the URL contains ?__sim
// Lets an offline harness fast-forward the real economy and read/drive it.
// VITE_SIM_HOOK=1 opts a production build in (scripts/capture-trailer.mjs builds that
// into dist-capture/ so the shipped bundle never carries the hook).
if ((import.meta.env.DEV || import.meta.env.VITE_SIM_HOOK === '1')
&& typeof window !== 'undefined'
&& /(\?|&)__sim\b/.test(window.location?.search ?? '')) {
;(window as unknown as { __SIM: unknown }).__SIM = {
store: useGameStore(),
tick, recalc, cleanup, fireRandomEvent,
setDtCap: (v: number) => { _tickDtCap = v },
}
}
A Node script drives a headless browser, calls tick with a raised dt cap, and reads the real store, not a re-implementation of the economy that can drift from the real one. That's the bit I'd recommend stealing: simulate the real code, not a model of it.
What it found: after the nerfs, content lasts roughly 8 to 20 hours, and there's a credits-negative trough somewhere around hours 3 to 8 that I never would have noticed by playing. I only found it because I could fast-forward.
2. The data told me I was fixing the wrong problem
I shipped opt-in PostHog analytics and looked at the first real numbers:
- ~40% of players left before completing their first goal
- Median playtime: 5 minutes
- The funnel cliffs were building → research → ship
I'd been agonizing over a 2-hour progression wall. The real problem was the first five minutes. No pair programmer, human or otherwise, was going to tell me that. Only data does.
One practical gotcha for anyone doing the same: my session_end event turned out to be effectively a heartbeat, so raw counts were inflated until I deduped. Check what your events actually mean before you trust a funnel.
3. Fast code generation grows dead code
When implementation is cheap, it's very easy to end up with a parallel, half-abandoned copy of your own app. I did. A "product cohesion" audit deleted a whole dead parallel app stack, merged two drifting friction models into one upkeep system, and merged two overlapping conquest features into one.
Rule I now follow: every time the AI adds something, I ask what it replaced. If the answer is "nothing," that's a smell.
4. Offline earnings are a design decision hiding in a number
Offline income is capped to up to 30 seconds of live production, with plasma excluded. That one line has generated more back-and-forth than most features, because it's balance, monetization (there's a rewarded-ad hook to boost it), and retention all at once. The code is trivial. The decision isn't.
What I'd tell another dev
- Give the AI a clear goal and a way to verify. It's fast when it can check its own work (tests, a sim, a build) and confidently wrong when it can't.
- Build measurement early. Analytics and a simulator will teach you more than any amount of playtesting on yourself.
- Treat deletion as a feature. Budget time for cleanup, because generation makes mess cheaper too.
- Never let a secret touch tool output. I learned that the hard way with a token. Pipe or assign directly, always.
- Expect your job to shift from "person who writes the code" to "person who decides what the code should do and checks that it did."
What's next
The next post in this series digs into the capture rig and how the virtual clock keeps trailers deterministic. If you've built an idle game or any economy-driven app, I'd love to hear how you approach balancing, and especially how you handle the first-five-minutes problem.
Galactic Idle is live on itch.io and in progress for Google Play. I'd genuinely love to hear where the first five minutes lose you.
Top comments (0)