First Vibe Code






Why this exists
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 balance harness paid for itself on its very first run
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.
Five defects that all looked like "this build is weak"
The balance milestone produced a whole series, and every one was invisible in the source:
- Resistances were impossible to stack. All five shared an exclusion tag with armour, so exactly one defensive affix could land per item. The 75% cap across five damage types was unreachable by construction.
physicalResistnever 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.- The tier ratchet only moved down. Climbing required two clears in a row, which is unreachable for a build that dies at all: 696 tier-1 clears without a single promotion.
- Paying life per hit had no cap. The cost scaled with attack speed; the damage bonus didn't. The blood build was dealing 250 damage per second to itself against 208 from monsters.
- Past the monster level cap, difficulty stopped growing entirely. The design promised the opposite, but there was nothing left to grow with: a build that broke tier 25 coasted to tier 2156.
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.
Half-translated is worse than untranslated
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;- same for the "s" on the surge timer and "/s" on the character sheet;
- the return card stored a pre-formatted string
'12.0 ч', so the time unit froze at whatever language was active on load and sat there in the middle of an English UI; - four behavioural affixes were named after themselves rather than their effect ("◆ of the feast"), even though the item card prints them alone on one line and says nothing else about them.
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.
Some colors are decoration, some are meaning
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.
The bag that only ate weapons
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.
Cutting the loot flood: my first answer was wrong
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.
What the release check caught
Two things, each of which would have sunk the launch or made it worse:
- The build was broken in a subdirectory. Vite emits absolute
/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 builtindex.htmland looking. - Mobile layout was broken. The two-column grid handed the sidebar its 260px minimum out of 375, leaving the scene a strip the width of a finger. The combat — the entire reason to open the game — was unreadable.
Decisions that were mine to make, not the code's
Several forks weren't technical, and the right move was to ask rather than pick:
- Rewarding goals. The code carried a reasoned "no rewards, deliberately": a reward turns goals into a chore list. The concern is right but aimed slightly off — a chore list is what you get when the reward must be collected. Here it's granted automatically, including while the tab is closed.
- Selling versus salvaging. Facts settled this one, not taste: there is no money in the game at all — no currency, no vendor, no prices — while crafting currency already exists and already drives the endgame. Selling would have meant inventing an economy for the sake of one button.
- Art direction. "Too plain and too light" admits several readings, and different readings mean completely different work. Cheaper to ask.
What the game deliberately doesn't have
- Per-ability skill trees. Four loadout slots and behavioural affixes cover that role.
- Multiple classes. One class, but deep: one unit of content work yields maximum variety, and no archetype ships half-finished.
- Positional combat. A pack, timers, formulas. Coordinates wouldn't add any decision the player actually makes.
- PoE-grade crafting. It would drag its own economy along behind it.
What I actually learned about vibecoding
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.
Files
Game 22
An idle ARPG that plays itself. You build the character; it does the fighting.
| Status | In development |
| Author | DonutDev |
| Genre | Role Playing |
| Tags | arpg, Idle, Incremental, Procedural Generation, Singleplayer |
Leave a comment
Log in with itch.io to leave a comment.