Posted August 17, 2026 by DonutDev
#vibecode #ai #game
I'm a developer who works by vibecoding — I describe what I want, let AI write most of the code, and spend my own attention on the decisions rather than the typing. Game22 is where I test whether that actually holds up on something real.
Not a to-do app. An idle ARPG with build depth: a stats engine with tagged modifiers, a deterministic simulation core, offline progress replayed by the real combat code, procedural graphics with no external assets, three languages. The kind of project where a wrong decision doesn't crash — it quietly ruins the balance three weeks later.
So the interesting question was never "can AI write this code". It was: can you keep a project like this honest when you didn't type most of it?
The answer I've landed on is: only if you refuse to trust anything you haven't measured. Almost every real defect below looked like "hmm, that build feels weak" and turned out to be a one-line structural mistake. Not one of them was found by reading code.
That's what this devlog is. Not a diary of features shipped — a list of things that turned out not to be what they looked like.
The first thing it surfaced looked like "this build is underpowered" and was structural: character life grew linearly against exponential monster damage. Those curves cross around level 40 — the classic way to break an ARPG. 1,126 deaths in two simulated hours.
The same run hid a second one: exponential mana cost against linear mana. Expensive spells became unplayable around level 20. In the report it read as "Fireball does no damage", when damage had nothing to do with it.
Each of those is about one line of code. You cannot find them by looking.
The balance milestone produced a whole series, and every one was invisible in the source:
physicalResist never reached combat. The stat rolled on affixes, was granted by a tree branch — and got dropped when the runtime was assembled. Every build played with 0% against physical.
Separately, the "Overwhelm" keystone turned out to be a trap rather than a choice: the same build reached T25 with it and T45 with "Retaliation" while dealing three times less damage. Attack speed in the endgame buys leech and ignites, not just damage — the keystone traded something useful for something that wasn't.
English and Spanish sat at 50%, and that wasn't "half done" — it was worse. The translated half was the UI and screens; the untranslated half was abilities, affixes, tree nodes and monsters. Which is exactly the content that appears in lists, side by side: players saw "Frost spike" sitting directly above "Огненный шар" in the same column.
Hence the rule: the unit of translation is a key group, not a key. An empty string is obvious at a glance. Mixed languages look like finished work and survive until someone complains.
Four bugs weren't caught by anything — not the build, not the coverage report, because the string is present and the type checks out:
◆ БОСС ◆ was drawn on the canvas as a literal and stayed Russian in every language;
'12.0 ч', so the time unit froze at whatever language was active on load and sat there in the middle of an English UI;
You don't fix that class of bug by proofreading. You fix it by making Cyrillic string literals in the presentation layers fail a test.
The UI moved from cold blue-grey to warm charcoal. That move exposed something nobody had planned for: the hero silhouette was painted with the UI accent color. The accent became brass — and the player blended into the brown monsters.
The first fix gave the hero blue steel. That landed 45 RGB units from the magic rarity color, and in a pack of blue monsters the player got lost again. The final answer is a turquoise no rarity uses, 95 units from its nearest neighbour.
Which produced my favourite test in the project: it checks distinguishability, not inequality. The first version of that test compared strings and would have passed both bugs.
Found on a release screenshot: all forty items in the bag looked identical. The icons weren't the problem. addToInventory evicted the weakest item by comparing scores across slots — while the docstring on the scoring function says, in as many words, that its values are only comparable within a slot.
The score is based on average weapon damage, which grows exponentially with item level; every other slot uses a flat constant. By the endgame that's 77,686 against 40. Weapons evicted everything. Measured result: forty weapons out of forty.
The function was being used outside its own contract, and the contract was written directly above it.
Too many items were dropping — close to 300,000 over 120 hours. The obvious move: cut the base drop chance. I cut it 45%.
Measured across three seeds, campaign pace became 11.8, 18.6 and 19.5 hours against a 6-hour tolerance. Not one unlucky seed — a systematic regression. The campaign runs on gear, and you can't take a third of the gear away from it. Softening the cut made it worse.
The excess was somewhere else entirely. The campaign drops 705 items in two hours — that's not a flood. The 300,000 accumulate in rifts, where quantity is multiplied by a per-tier bonus. Cutting that instead doesn't touch the campaign at all.
The result beat the original on all three measures at once: half the items, campaign pace 0.7–3.6h against the previous 0.8–4.6, and 10 of 10 builds reaching T30+ against 9.
Two things, each of which would have sunk the launch or made it worse:
/assets/… paths by default, and itch.io unpacks into /html/12345/. Players would have seen a blank page. One line fixes it (base: './'), but you can only notice by opening the built index.html and looking.
Several forks weren't technical, and the right move was to ask rather than pick:
It moves fast and it lies confidently. Both halves matter. The code arrives working and plausible; the structural mistake is invisible inside it and stays invisible until something measures it.
So the harness isn't a nice-to-have here, it's the whole safety net. Every number change gets a 120-hour simulation run compared against a stored baseline. Every class of bug that survived the build, the tests and the coverage reports got a new test written for that class — mixed-language groups, colliding entity names, Cyrillic literals in the render layer, hero color versus rarity colors.
The rule I ended up with: if a mistake can't be seen by the build, the tests or the reports, then the job isn't done until something can see it. Everything above is a receipt for that rule.