After getting the bullet behaviors in DET-33 into a good place, the next system to crack was skills: the active abilities a player picks up as they level. I’d built ability systems before and always regretted the same thing, so this time I restructured them to be stat-agnostic from the start.
The mistake I keep making
The tempting way to define a skill is to reach into player stats. “Healing scales with your wisdom, damage scales with your level.” It feels right and it ruins you later. Every skill becomes coupled to the stat model, balancing one thing ripples into everything, and you can’t reason about a skill in isolation. Years ago I wrote about balancing game mechanics by drawing the tree of which stats feed which. The lesson I took from doing that is that the fewer edges in that tree, the better, and skills reading stats directly add a lot of edges.
So in DET-33 a skill is defined entirely by data. It lives in a JSON file and carries its own numbers:
{
"name": "Combat Medkit",
"quality": "rare",
"affects": [{
"properties": {
"type": "heal",
"damage": "5d8+8",
"cooldown_in_ms": 1500,
"visual_effects": ["skillArea", "sparkles", "floating_text"]
}
}]
}
No stat lookups. The skill says what it does, including its own dice expression, cooldown, and the visual effects to play. Quality tiers (common through legendary) are just different rows with bigger numbers, so Medkit, Advanced Medkit, and Combat Medkit are the same skill growing up.
Behavior by category, not by skill
Execution uses the same trick the bullets do. A skill names a type (heal, laser, melee, ranged, summon, repulsion, decoy, trap spawner, trail spawner, stat modifier, effect applicator) and a registry maps that type to a behavior class. There are eleven behaviors covering every skill in the game. Add a new skill and most of the time you’re adding a JSON row, not code, because some existing behavior already does the thing.
The “agnostic” part pays off in the weird cases too. Mob-affecting skills and traps emit effects rather than poke at stats, so a confusion effect that orbits an enemy and a control-inversion trap that scrambles the player’s input are the same kind of thing wearing different durations. Because nothing is wired to a specific stat, I can drop an effect on a player, an enemy, or a summon and it just works.
It’s not a glamorous system to demo, but it’s the one that lets me add abilities for an afternoon and not spend the next week untangling what I broke.
