World-Builder Guide
Monster Design Guide
Author monsters that are fair, readable, and fun — grounded in the live combat math.
How to author monsters that are fair, readable, and fun — not damage sponges, not one-shot machines, not zero-threat furniture. Every number here matches how the game actually plays. Pairs with Item System guide — the field-by-field JSON reference for what each key is.
0. The one rule
A monster is tuned against a specific player, at a specific level, with specific gear. "Is this monster too strong/weak?" is meaningless until you name the player you mean. This guide gives you that reference player per tier, so you can always compute the answer instead of guessing.
1. The math you're tuning against
This is exactly how combat resolves. You can't design a fair monster without it.
Player damage → monster
physical = max(0, floor(Strength × scaling) − monster.Defense)
magic = max(0, floor(Intelligence × scaling) − monster.Resistance)
then × the elemental multiplier.
Monster damage → player
base = random(damage_min .. damage_max) # one roll per attack
scaled = floor(base × attack.scaling_factor)
hit = max(0, scaled − player.Defense|Resistance) # Def for physical, Res for magic
hit = hit × player's elemental multiplier # players are ×1 today (no resist gear)
then the QTE can reduce it further (dodge/parry).
⚠️ Mitigation is FLAT SUBTRACTION, not percentage
This is the single most important fact for not writing a monster that sucks. A few points of defense completely null out a low roll. A monster whose floor(roll × scaling) doesn't clear the target's defense by a healthy margin does zero and is a boring waste. Always tune damage against the expected defense at that level (§3), not against a bare level-1.
Player power by level
- Base (L1): HP 100, MP 50, STR 10, INT 10, DEF 5, RES 5, SPD 10.
- +3 allocatable points per level; 1 pt = +1 to an attribute, or +10 HP / +5 MP.
- XP curve:
XP(level) = 100 × (level−1)². So L5 = 1600 XP, L10 = 8100 XP. - XP per kill:
10 × tier²→ T1 = 10, T2 = 40, T3 = 90.
2. Tiers are keyed to expected player level
The engine allows tiers 1–10. This bundle uses 1–3. A tier is a promise about who fights this monster:
| Tier | Expected level | Role | Reference player (see §3) |
|---|---|---|---|
| 1 | L1–4 | trash / fodder — you meet these first, in packs | Novice |
| 2 | L4–8 | elite / mini-boss — a real threat, killable solo with care | Adventurer |
| 3 | L8–12 | zone boss — a gear+skill check, better with friends | Veteran (+group) |
| 4–10 | ~+4 levels/tier | future / federated end-game | scale §3 forward |
Progression pace check: reaching L5 (1600 XP) ≈ 40 tier-2 kills or ~18 tier-3 kills — i.e. clearing the early/mid zones. By the maze/cave_4 bosses a player is plausibly L5–10, which is why tier 3 targets L8–12 or a group of slightly lower players.
3. The reference player per tier
A "median" player for that band: some points spent on a combat build + tier-appropriate gear. Tune every monster against its tier's reference player.
| Novice (T1, L1–4) | Adventurer (T2, L4–8) | Veteran (T3, L8–12) | |
|---|---|---|---|
| HP | ~110 | ~180 | ~270 |
| STR / INT | 13 / 11 | 19 / 15 | 28 / 24 |
| DEF / RES | 7 / 6 | 14 / 9 | 20 / 15 |
| ability scaling | ~1.0 | ~1.2 | ~1.5 |
| gear assumed | starter (iron sword, leather) | steel sword, scale armor, life amulet | gauntlets/dragon scale/archmage tier |
4. Stat budget per tier (author to this)
Numbers are the monster's stats. "Lands/hit" and "TTK" are computed against the §3 reference player and are the real targets — hit those, not the raw numbers.
| Stat | Tier 1 | Tier 2 | Tier 3 (boss) |
|---|---|---|---|
| HP | 20–55 | 55–95 | 120–200 (bosses use phases, §7) |
| damage_min | 6–10 | 12–16 | 16–22 |
| damage_max | 10–16 | 18–26 | 26–40 (burst attacks) |
| defense | 0–4 | 4–7 | 8–12 |
| resistance (general) | 0–2 | 2–5 | 8–12 |
| scaling_factor | 0.8–1.0 | 1.0–1.5 | 1.0–2.5 (multi-phase) |
| speed (feel knob, §5) | 5–8 | 5–9 | 5–7 |
| aggro_radius | 4–7 | 5–8 | 6–10 |
| respawn_time (s) | 25–60 | 30–120 | 300–3600 |
Target feel (what those budgets should produce)
| Tier 1 | Tier 2 | Tier 3 | |
|---|---|---|---|
| effective dmg/hit vs ref player | ~2–7 (2–6% HP) | ~5–12 (4–8% HP) | ~8–20, burst to ~35 (up to ~15%) |
| player TTK (turns to kill it) | 3–5 | 5–8 | 8–15 solo · 4–7 grouped |
Hard invariants (a linter could enforce)
- I1 — Damage clears defense.
floor(damage_max × scaling) − refDefense ≥ 3. If a
monster can roll a 0 against its own tier's reference player, it's under-tuned.
- I2 — No trash out-tanks a boss. A tier-N monster's
defensemust not exceed the
tier-(N+1) floor. (The old skeleton had def 10 > the tier-3 boss's 8.)
- I3 — Tougher tier isn't squishier.
tier2.hp ≥ tier1.hp_max. (fire_spirit at 35 HP
was squishier than a 50-HP goblin.)
- I4 — Resistances stay in [0.0, 2.0]. 0 = immune, 2 = double. No "holy: 10".
- I5 — Respect drop caps for global items (Item System guide §10): T1 0.10,
T2 0.05, T3 0.02. Soulbound is uncapped.
How to sanity-check a monster in 30 seconds
- Compute its dmg on the ref player:
floor(damage_max × scaling) − refDEF. In the feel band above? - Compute ref player's dmg on it:
floor(refSTR × abilityScaling) − monster.DEF, divide HP → TTK in the band? - If either is ~0 or wildly off, retune. Done.
5. Movement speed = a feel knob, NOT power
A monster takes a step roughly every quarter-second, at a rate set by its speed:
step rate ≈ speed × 0.4 tiles/sec. speed 1 = 0.4 t/s (crawls), speed 8 = 3.2 t/s (smooth), speed 10 = the max useful value (higher is wasted).
- Speed does not affect damage or HP; in combat it only breaks ties in turn order.
It moves monsters only — player movement is input-driven.
- Don't author below ~5 — it feels broken/sluggish. Tier by archetype: heavy (golem,
knight) ≈ 5, normal (goblin, mage) ≈ 6–7, fast (rat, shadow) ≈ 8–9.
- Speed is also the chase gate, so a fast monster catches and pulls you into combat
faster. When you raise speed, trim aggro_radius a notch or it becomes inescapable.
6. Attacks & QTE — the moment-to-moment fun
Full field reference in Item System guide §7. Design guidance:
Attack type decides who gets hit
targeted→ one random living player. Damage spreads in a group (each hit 1/N).aoe/global→ every player. No spreading; grouping only helps via faster kill.- Default to
targeted. It's what makes grouping safer (§8). Reserveaoe/global
for deliberate "everyone's in danger" beats, not as the whole kit.
It's ANTICIPATION, not reaction
The QTE is not a flash you react to. When a monster attacks, the game telegraphs it: a marker sweeps around a ring toward a fixed target zone, and it starts up to ~1.5 seconds before the window opens. The player watches it approach and presses when it lines up — so the skill is rhythm and anticipation, not reflex. Near-perfect timing is a reachable ceiling, not luck. Don't design around "reaction time": there is plenty of time to see the hit coming — the challenge is nailing the exact moment.
The scoring model — a parry is a razor's edge
Damage negation is continuous, not all-or-nothing:
- Full negation happens only in a tiny perfect dead-zone at the exact centre of the
window (about ±20 ms).
- Miss the centre and some damage leaks through — the further off, the more you take,
ramping up to a full hit at the outer edge (about ±200 ms out).
- So a "parry" that isn't near-perfect still hurts. Treat perfect timing as the only real save.
You author two numbers per attack:
parry_window_ms— the tolerance for a clean parry. The perfect dead-zone is about a
quarter of this.
dodge_window_ms— the outer edge; past it, the hit lands in full.
Both are ± tolerances around the window's centre — not durations.
Windows only get HARDER, never easier
There's a hard floor: the perfect zone can't be wider than about ±25 ms and the outer edge can't be wider than about ±220 ms, no matter what you write.
- Authoring a wider window does not make an attack easier — it clamps to the
demanding baseline (parry 80 / dodge 200).
- Authoring a tighter window does make it harder.
So there's no such thing as a "generous" window. You tune difficulty up from an already-hard floor by tightening; leaving the defaults is the easiest an attack can be.
| Tier | parry | dodge | feel |
|---|---|---|---|
| 1 | 80 (default) | 200 (default) | the hard baseline — already demanding |
| 2 | 60 | 160 | tighter |
| 3 | 40 | 120 | punishing — for boss burst beats only |
Two rules that keep the telegraph honest:
- Set parry/dodge at the top level of the
qte, not per-input. A per-input override
makes the on-screen ring disagree with the actual result (it can read MISS but resolve as a parry). Keep them top-level.
- Centre the window on the tolerance:
window_start = centre − parry,
window_end = centre + parry, so the zone the player sees matches how it scores.
Fighting above your level is lethal
A monster's outgoing damage scales with its power tier — roughly ×1.6 for each tier above the player (a tier-2 hits about 1.6× as hard as a tier-1, a tier-3 about 2.6×, and so on). Same-tier fights are unaffected — so tune against the reference player (§3) as normal. But someone who wanders into a zone above their level meets damage that dwarfs their HP, and because even a good-but-not-perfect parry leaks a little, a single hit can kill them. That "don't punch above your weight" wall is automatic — you don't build it, and you shouldn't try to fake it with a huge damage_max.
The player's own strike timing
Attacking uses the same skill in reverse. After choosing an action and target, the player times a press on a one-second strike sweep: perfect timing lands 1.5× damage, sliding down to 0.5× at the edge, and no press at all = 0.5× (a weak hit — you have to engage). The TTK figures in §4 assume roughly neutral strikes (~1.0×): a skilled player clears faster, a passive one much slower.
Damage × QTE together
A tight QTE on a hard-hitting attack is the real threat. A tight QTE on a 0-damage attack is just annoying. Scale window tightness with the damage that lands (§4/I1) — and since the floor is already hard, reserve tier-3 tightness for attacks whose damage justifies demanding a near-perfect save.
7. Multi-phase bosses & the trigger system
A boss is an entity with multiple phases; each turn the engine picks the highest-index phase whose trigger fires, re-evaluated with fresh (HP%, turn count, living player count). Triggers AND together; an all-zero trigger always fires (that's phase 0).
There are three trigger types, all supported:
| Field | Fires when | Use for |
|---|---|---|
hp_pct_below | HP% < X | escalation ("enrage at 30%") |
turn_count_above | turn > X | enrage timer / DPS check |
players_alive_below | living players < X | group gating / anti-attrition |
Design notes:
- Order phases so the nastiest is the highest index (it wins when multiple fire).
- Enrage timer (
turn_count_above): a fight the group must win by turn N, or a
brutal phase turns on. The clean way to make a long solo attempt lethal (§8).
- Anti-solo (
players_alive_below: 3): a berserk phase that fires whenever fewer
than 3 are alive — solo/duo trip it immediately. Use only for designated raid bosses.
- Not expressible today: monster self-heal, shields, threat/aggro/taunt, hard party-
size entry locks. Don't design a boss whose fairness depends on them.
8. Making a monster "better with friends" (no new mechanic)
Grouping already helps two ways, automatically:
- DPS stacks — N players → ~1/N the turns → far fewer dangerous QTEs endured.
- Targeted damage spreads — a
targetedattack hits 1 of N, so each player is hit
1/N as often.
So solo = a long fight where you must win every QTE; grouped = a short fight where danger is shared and allies finish the kill if you slip. To lean a monster that way:
| Lever | Effect |
|---|---|
| High HP | long solo TTK (attrition risk); a group's stacked DPS keeps it short |
Mostly targeted attacks | damage spreads 1/N across the party |
| Tight QTEs | one slip over a long solo fight = death |
| Big per-hit damage | tanking every hit solo is lethal; grouped you tank a fraction |
Recipe: HP so solo TTK ≈ 15–20 turns but 3-player ≈ 5–7 · pool mostly targeted, one aoe for pressure · tight QTEs · optional turn_count_above enrage as the hard check.
Trap: an all-global boss barely rewards grouping for survival (everyone eats every hit) and just becomes a DPS race — often harder to coordinate. Keep it targeted.
8.5 Reinforcement / add-swarm encounters (a soft timer made of bodies)
Some encounters are hard not because one monster is strong, but because more keep joining the fight — a horde-anchor pattern. This is fully emergent from existing systems; there is no "summon" ability:
- An anchor monster (e.g. a mage/boss) sits near several add spawns.
- While you fight the anchor, the adds'
chase_playerbrain walks them toward you. - Join-on-contact: a monster that reaches a player already in combat is added to
that combat's roster mid-fight — it joins the turn order on the next turn.
- Adds
respawn_timerefreshes the horde, so waves recur.
The soft timer = how long until the next wave lands:
X ≈ add.respawn_time + (distance_to_fight ÷ (add.speed × 0.4 tiles/sec))
Kill the anchor before X and you fight it clean; take too long and a fresh wave joins — which is exactly the dark_mage in cave_4, ringed by skeleton spawns today.
Why this is a prime "bring friends" tool
A group bursts the anchor before the horde compounds, or splits off to intercept adds. Solo, you can't out-DPS the anchor and survive the accumulating adds — the swarm is the wall. It stacks naturally with §8 (each add's targeted hits spread across the party).
How to author one (it's level layout, not stats)
- Place 2–4 add spawns within chase+travel range of the anchor (same map, a room or two away).
- Anchor HP sets the burst window — big enough that a solo can't clear it before wave 2.
- Add
respawn_time30–60 s → waves recur on a felt cadence (too short = grind, too long = free kill). - Keep each add a fair tier-appropriate mob (§4). The swarm is the threat — do not
overtune the individual add, or it's just unfair (the old def-10/dmg-30 skeleton × a horde was a death trap).
- Map geometry matters: a chokepoint near the anchor lets a group hold the line
(adds arrive single-file); open ground lets them swarm. Design the room to the intent.
Live example — cave_4 (dark_mage + skeletons)
This is already a reinforcement encounter and should stay one: the dark_mage anchor with skeletons trickling in. Now that skeletons are retuned to fair tier-2 (def 5, dmg 8–14, spd 8, holy-weak — the balance notes), the horde is a legitimate group gate rather than a death trap. The skeleton count is the mechanic — don't blanket-cut it; tune the per-add stats and the respawn cadence instead.
Limits
- No explicit summon/
add-spawnaction — reinforcement is spawn-proximity + respawn +
chase + join. You tune it in spawns.json (placement, count, respawn), not on the boss.
- Adds must physically reach the player to join, so pathing/geometry is part of the tuning.
9. Anti-patterns — "monsters that suck" (a checklist of real mistakes)
Every one of these was found live in this bundle. Don't repeat them.
- ❌ Damage below the target's defense → does 0, boring. (Flat mitigation, §1/I1.)
- ❌ Out-of-tier stats — trash that out-tanks/out-hits the boss. (skeleton: def 10, dmg 30.)
- ❌ Tier inversion — a "higher" tier squishier than a lower one. (fire_spirit 35 HP < goblin 50.)
- ❌ Stat-clones with different names — no power step between them. (goblin vs "goblin warrior", identical.)
- ❌ Sluggish speed (1–3) — feels broken. Floor at ~5. (§5.)
- ❌ Backwards/extreme resistances —
holy: 10(10× damage). Keep in [0,2]. (§4/I4.) - ❌ All-
globalattack kit — negates group play, becomes a coordination-punishing DPS race. (dark_mage.) - ❌ Designing QTEs around reaction time — the attack telegraphs for up to ~1.5 s with a
visible sweep; it's an anticipation test, not a reflex one. "Generous ±200 ms" windows are a myth — the engine clamps them to the hard baseline anyway (perfect ±20, edge 200). Tune tighter for harder, never wider expecting easier. (§6.)
- ❌ Per-input parry/dodge overrides — the client preview only reads the top-level, so the
on-screen ring disagrees with the server ruling (parry reads MISS then resolves PARRY). Top-level only. (§6.)
- ❌ God-tier drops / debug items in loot — e.g. a ring with +500 stats. Pull them. (ring_of_the_mind.)
- ❌ Damage-sponge with no phases — huge HP, flat attacks, no escalation → tedium. Give it phases (§7).
- ❌ Defined but never spawned — dead content. If it exists, place it. (poison_mamba, shadow_stalker.)
- ❌ Overtuned adds in a swarm — a reinforcement horde must be fair per add; the count
is the threat, not each body. (A def-10/dmg-30 skeleton × 6 was a death trap; §8.5.)
- ❌ Gutting an intentional add-swarm — cutting the horde to a single mob kills the
encounter. Tune respawn cadence + per-add stats instead of deleting adds. (§8.5)
10. Worked examples (author to the reference player)
Tier 1 — a proper goblin (target: Novice, L1–4)
HP 55 · dmg 8–14 · def 3 · scaling 1.0 · speed 6 · aggro 5 · targeted slash, generous QTE.
- Its hit on Novice:
14 − 7 = 7max (≈6% HP). ✅ feels like a threat, not lethal. - Novice's hit on it:
floor(13×1.0) − 3 = 10→ 55 HP ≈ 5 turns. ✅ in band.
Tier 2 — a shadow stalker (target: Adventurer, L4–8; fast skirmisher)
HP 60 · dmg 12–18 · def 4 · scaling 1.5 · speed 9 · dark element · targeted, tight QTE (~250/120 ms).
- Its hit on Adventurer:
floor(18×1.5) − 14 = 13(≈7% HP), tight QTE makes it bite. ✅ - Adventurer's hit:
floor(19×1.2) − 4 = 18→ 60 HP ≈ 4 turns. Fast + hits hard = respect. ✅ - Teaches the holy-beats-dark counter (holy attacks ×2 on it).
Tier 3 — a "bring friends" zone boss (target: Veteran L8–12, or a group)
Phases:
{}— HP 600 · swing dmg 15–25 ×1.2 physicaltargeted· def 10 · speed 5.{ "hp_pct_below": 40 }— adds beam dmg 15–25 ×2.0 magictargeted, def 12.{ "turn_count_above": 14 }— ENRAGE:globaldmg 20–30 ×2.5 (the DPS check).- Beam on Veteran:
floor(20×2.0) − 15 = 25/hit (≈9% HP) — real phase-2 pressure. - Veteran DPS:
floor(28×1.5) − 10 = 32/hit → 600 HP ≈ 19 turns solo (past the turn-14
enrage → death) but ≈ 6–7 turns for a trio → beat it before enrage. ✅ exactly the "better with friends" shape, using only existing mechanics.
11. Author checklist (before you ship a monster)
- Named the tier and its reference player (§2/§3).
- Ran the 30-second check (§4): its dmg on ref player, and ref TTK — both in-band.
floor(damage_max × scaling) − refDEF ≥ 3(I1 — it actually hurts).- No invariant broken (I2 out-tank, I3 squishy-tier, I4 resistances, I5 drop caps).
- Speed ≥ 5, tiered by archetype; aggro trimmed if fast (§5).
- Attack mix mostly
targetedunless you mean to hit the whole party (§6/§8). - QTE parry/dodge set at top level (not per-input), centred (
win = centre ± parry). Left at the80/200default for a hard-baseline attack, or authored tighter for a deliberately harder one — never wider expecting "easier" (the caps clamp it) (§6). - Bosses have phases with escalation; enrage/
players_alive_belowonly where intended (§7). - If it's an anchor/add-swarm, adds are fair per-body and placed for the soft-timer intent (§8.5).
- It's actually spawned somewhere, with a working sprite (
cols×4, Item System guide / render). - JSON parses; loot within caps; every ref (
entity_def_id,item_def_id, sprite) resolves.