Files
Emberfall/docs/BALANCE.md
2026-10-20 20:33:35 +11:00

116 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BALANCE.md — Formulas & Starter Numbers
All numbers below go in `src/game/constants.js`. Tune there, not inline.
## Energy
```
ENERGY_MAX = 100
ENERGY_REGEN_SECONDS = 180 // +1 per 3 min
ENERGY_TONIC_RESTORE = 25
```
```
computeEnergy(stored, updatedAtMs, nowMs):
if nowMs <= updatedAtMs: return {energy: stored, updatedAtMs}
gained = floor((nowMs - updatedAtMs) / (REGEN_SECONDS*1000))
if stored + gained >= MAX: return {energy: MAX, updatedAtMs: nowMs}
return {energy: stored + gained, updatedAtMs: updatedAtMs + gained*REGEN_SECONDS*1000}
```
(Keep leftover partial interval by advancing the timestamp only by whole ticks.)
## Leveling
```
xpToNext(level) = floor(100 * level^1.5) // L1→2: 100, L2→3: 282, L5→6: 1118, L10→11: 3162
MAX_LEVEL = 50
STAT_POINTS_PER_LEVEL = 3
```
On level-up: `hp_current = maxHp`, loop to handle multiple level-ups from one reward.
## Derived stats
```
maxHp = 50 + totalVit*10 + level*5 // totalVit = base vit + equipment vit (counted ONCE)
primary = STR | INT | AGI (by class)
attack = 5 + primary*2 + level + equipment.bonus_attack
defense = floor(vit*1.5) + equipment.bonus_defense (+10% if Warrior)
speed = 10 + agi
critChance% = min(40, 5 + floor(agi*0.5)) (+8 if Ranger)
critMult = 1.5
```
Equipment `bonus_str/agi/int/vit` are added to the base stats BEFORE the derived formulas, and nowhere else (never count an equipment bonus twice).
## Damage
```
mitigation = 100 / (100 + defenderDefense)
raw = attackerAttack * mitigation
variance = rng.range(0.9, 1.1)
dmg = max(1, round(raw * variance * (crit ? critMult : 1)))
```
Mage bonus: if defenderDefense >= 15, multiply `raw` by 1.15.
Monsters crit at a flat 5%.
## Rewards
```
xp = monster.xp * levelDiffMult
gold = rng.int(monster.gold_min, monster.gold_max) * levelDiffMult
levelDiffMult = clamp(1 + 0.1*(monster.level - hero.level), 0.2, 1.5)
```
Loot: for each row in `loot_table` for that monster, roll `rng.float()*100 < chance_pct`.
Max 2 distinct loot drops per fight.
## Defeat penalty
`goldLost = floor(gold * 0.10)`; `hp_current = 1`; `deaths += 1`.
## Rest
`restCost = level * 5` gold → heals to full. Free if hp already full is rejected (`NOTHING_TO_HEAL`).
## Starter monsters (use or tweak)
| Zone | Name | Lv | HP | Atk | Def | Spd | XP | Gold |
|---|---|---|---|---|---|---|---|---|
| Meadows | Giant Rat | 1 | 20 | 6 | 1 | 12 | 8 | 1–3 |
| Meadows | Meadow Wolf | 2 | 32 | 9 | 3 | 16 | 14 | 2–5 |
| Meadows | Goblin Scrapper | 3 | 45 | 12 | 5 | 11 | 22 | 4–9 |
| Forest | Webspinner | 5 | 70 | 18 | 8 | 15 | 40 | 8–16 |
| Forest | Bandit Cutthroat | 6 | 90 | 22 | 12 | 14 | 55 | 12–24 |
| Forest | Mossback Treant | 8 | 150 | 26 | 22 | 7 | 85 | 15–30 |
| Crags | Ember Imp | 10 | 130 | 34 | 15 | 20 | 120 | 25–45 |
| Crags | Stone Golem | 12 | 260 | 38 | 40 | 6 | 170 | 30–60 |
| Crags | Ashfang Drake | 14 | 300 | 50 | 30 | 18 | 260 | 50–100 |
## Starter items (≥ 20 required; this is a minimum seed list)
| Item | Type | Rarity | Lv | Buy | Bonus |
|---|---|---|---|---|---|
| Rusty Sword | weapon | common | 1 | 20 | +3 atk |
| Apprentice Staff | weapon | common | 1 | 20 | +2 int, +1 atk |
| Hunter's Shortbow | weapon | common | 1 | 20 | +2 agi, +1 atk |
| Iron Blade | weapon | uncommon | 5 | 120 | +8 atk, +2 str |
| Oakwood Staff | weapon | uncommon | 5 | 120 | +6 atk, +3 int |
| Yew Longbow | weapon | uncommon | 5 | 120 | +6 atk, +3 agi |
| Emberforged Greatsword | weapon | rare | 10 | 600 | +18 atk, +4 str |
| Leather Jerkin | armor | common | 1 | 25 | +3 def |
| Chainmail Vest | armor | uncommon | 5 | 140 | +9 def, +2 vit |
| Drake-scale Armor | armor | rare | 12 | 700 | +22 def, +4 vit |
| Wooden Charm | trinket | common | 1 | 30 | +1 vit |
| Wolf-fang Pendant | trinket | uncommon | 3 | 90 | +2 agi |
| Ember Ring | trinket | rare | 10 | 450 | +3 str,+3 int |
| Minor Healing Potion | consumable | common | 1 | 15 | effect heal:50 |
| Greater Healing Potion | consumable | uncommon | 8 | 60 | effect heal:150 |
| Energy Tonic | consumable | uncommon | 1 | 80 | effect energy:25 |
| Rat Tail | material | common | 1 | 0 | sell 2g |
| Wolf Pelt | material | common | 2 | 0 | sell 5g |
| Spider Silk | material | uncommon | 5 | 0 | sell 12g |
| Imp Horn | material | uncommon | 10 | 0 | sell 30g |
| Drake Scale | material | rare | 14 | 0 | sell 90g |
Materials with `buy_price = 0` sell at: common 2×level, uncommon 4×level, rare 8×level (round down, min 1).
## Starter loot chances
Common material from its zone's weakest monsters: 40%. Uncommon gear drops: 3–5%.
Rare gear drops: 0.5–1% from top-tier zone monsters. Healing potion: 8% anywhere.
## Expedition starters
| Name | Min Lv | Duration | Energy | XP | Gold |
|---|---|---|---|---|---|
| Scout the Old Road | 1 | 10 min | 10 | 40 | 10–25 |
| Raid the Bandit Camp | 5 | 1 h | 25 | 300 | 80–160 |
| Delve the Hollow | 10 | 4 h | 40 | 1400 | 300–600 |
(Use short durations such as 20–60 s via an env flag `DEV_FAST_TIMERS=1` for testing.)