Module balance

Source
Expand description

Functions and constants here mirror the original balance.yaml script, which contained the project’s fundamental combat and economic balance formulas. Content-dependent lookups (item rarity quality, ability rarity effectiveness, fixed-power items) are sourced from ContentLookups, which is populated at engine init time by reading the content_raw module’s data maps.

Structs§

BalanceTuning

Constants§

AOE_COEF
ATTACKS_PER_SEC
ATTR_DEVIATION
AUX_ATTR_IMPACT
BALANCE_TUNING_DEFAULT
Tuning with every knob at its constant default.
BASE_ATTACK
BASE_CRIT_CHANCE
BASE_CRIT_MOD
BASE_HP
BASE_POWER
BASE_SPEED
BASE_SPELL_EFF
BRAVERY_BUFF_DURATION
BRAVERY_BUFF_QUANTITY
BUFF_EFF
CASTS_PER_SEC
CHAPTER_POWER_STEP
CLASS_COUNTER_SWING
COUNTERATTACK_POWER
DECEIT_DEBUFF_DURATION
DECEIT_DEBUFF_QUANTITY
DMG_K
DOT_TICK_MAX_PCT
EFF_A1
EFF_A2
EFF_B1
EFF_B2
EFF_C1
EFF_C2
EFF_MIDGAME_LEVEL
ENEMY_CURVE_ANCHOR_CHAPTER
ENEMY_CURVE_ANCHOR_POWER
ENEMY_EARLY_BUMP
Early-game stat-check bump strength. 1.0 = OFF (current). Was 1.6 (2026-06-23, “too easy early, full HP”) but playtest (2026-06-29) found the bump created the opposite problem: stages 1-3..1-5 felt hard and punishing (“cleared by luck”, “barely scraped through”), while 1-7+ felt fine. The sim confirmed ch2 (=stage 1-3) already hits P/E=1.24 with min-HP 13% from the hand-made→formula transition alone, and the ch3=1.6x bump pushed ch3 (=stage 1-4) from the clean 1.30x step to a 2.09x step — creating an anomalous hump. Disabled (1.0) so the geometric 1.30x step runs cleanly through early chapters. Tune via OVERLORD_BAL_ENEMY_EARLY_BUMP.
ENEMY_MID_BUMP
Mid-game enemy bump — applied only to CampaignBossFight (ch7–15). Applied via OVERLORD_BAL_ENEMY_MID_BUMP. At 8.0 the boss at ch7 has effective power 687 × STEP^0.5 × 8 ≈ 6265 vs a frozen player’s ~2121 (P/E ≈ 0.34 → ~7% first-try win rate), while an engaged player (power ~3274) has P/E ≈ 0.52 — with stop_on_lose=true and boss-stop-on-loss retries, even a 50% single-shot rate means the free bot quickly clears it via quest-funded chest opens between attempts, while the lazy bot has no path to more power. Combined with stop_on_lose=true on campaign boss fights and the lazy-bot strategy not retrying boss fights, this creates the hard engagement gate: freeze at 1-6, wall at the ch7 boss. Wave fights are unaffected (the bump is applied in enemy_power_scalar, not in enemy_power_for_chapter), so the global power curve stays monotone.
ENEMY_STEP_LATE
ENEMY_STEP_TAPER_START
FIGHT_DURATION
HEAL_COEF
HOT_TICK_MAX_PCT
Per-tick caps for HoT/DoT effects, as a fraction of max HP — safety so a mis-tuned ability effect can’t out-heal all damage (HoT) or one-shot (DoT). Generous (these are ability-derived, already bounded by ability balance); the cap only catches pathological values. Seed; sim-calibrated.
K_ARMOR
K_DODGE
MAIN_ATTRS_QUANTITY
MIN_RECEIVED_DAMAGE_K
Minimum received_damage multiplier (in /10000 units) so heavy mitigation (e.g. stacked protection) can’t reach ≤0 → unkillable. 100 ⇒ ≤99% reduction.
MIN_STAT_MOD_MULT
Floor on a stat’s .mod multiplier in get_entity_stat, so a stacking .mod debuff (e.g. weakness on attack, protection on received_damage) can’t drive a stat to ≤0 (zero-damage / unkillable). 0.05 ⇒ ≤95% debuff. Only bites at mod ≤ −9500; no-mod / buff stats are unaffected.
OT_COEF
POWER_NORM
Power-scalar normalisation. Anchors a reference character (attack=BASE_ATTACK, hp=BASE_HP, neutral elsewhere) at BASE_POWER, so the displayed Combat-Power integer stays on the established scale while the formula underneath is P = DPS × EHP. = 600·3000/1000 = 1800.
REGEN_TICK_MAX_PCT
Default cap on per-tick combat regen, as a fraction of max HP (seed; final from the bot-sim). The Regeneration_rate effect fires ~1×/s over an ~8.3s FIGHT_DURATION (~8 ticks), so this bounds a fight’s combat regen to ≈0.16×HP ⇒ ≈×1.16 EHP. The power scalar’s regen-as-EHP is derived from this same value (power_from_attrs, SC-1 fix) so display/matchmaking can’t over- rate what combat delivers — there is no separate scalar regen cap any more.
SELL_PRICE_COEF
SELL_PRICE_EXP
SPELL_QUANTITY

Functions§

ability_damage_from_id
ability_damage_from_rarity
ability_eff
armor_k
attr_spread_for_item
attr_spread_random
aux_attr_eff
aux_attr_eff_for_item
bravery_p_from_eff
buff_uptime_mult
Multiplicative buff/debuff factor from a proc chance p (0..1): a buff of strength BUFF_EFF held for a mean uptime, stacked quantity times. Shared by bravery (offensive) and deceit (defensive); preserves the legacy (1 + (BUFF_EFF−1)·uptime)^quantity shape exactly.
cap_per_tick
Per-tick heal/damage cap: amount bounded above by pct × max_hp. Used by the regen / HoT / DoT combat ticks so a mis-tuned effect can’t out-heal all incoming damage (regen/HoT) or one-shot (DoT). Pure + testable; pct comes from the matching tuning() knob (regen|hot|dot_tick_max_pct).
character_attrs_power
The attrs_power term of character_power: char-level attributes, inventory items and pet stats composed into an AttrMap and run through power_from_attrs — WITHOUT the trailing * ability_eff_sum multiplier.
character_power
references. The previous version accepted either real essences structs (the into these typed inputs and then calls this exact function — so results are byte-identical on both paths.
character_power_from_attrs
Combat-power scalar from an already-composed AttrMap plus equipped abilities — floor(power_from_attrs(attrs) × Σ ability_eff).
class_counter_multiplier
Soft 3-cycle damage multiplier for an attacker class hitting a defender class (both Option, resolved via lookups.class_counter_role). Returns 1 + CLASS_COUNTER_SWING when the attacker’s role beats the defender’s, 1 - CLASS_COUNTER_SWING when it is beaten, and 1.0 otherwise (same role, either neutral, or a classless PvE mob).
class_counter_role_from_code
3-cycle combat role from a class’s main_attribute code. 0 = Warrior, 1 = Rogue, 2 = Mage, -1 = neutral.
day_by_level
deceit_p_from_eff
eff_by_level
eff_item_with_config
eff_spell_by_level
effect_cost
effect_duration
enemy_power_for_chapter
Absolute enemy power at chapter: ANCHOR · ∏ step(c), where the per-chapter step is the constant chapter_power_step until enemy_step_taper_start, then decays smoothly toward enemy_step_late so late-game difficulty tracks the player’s decelerating power (no multi-day stalls). With the taper OFF (enemy_step_late >= chapter_power_step) this is the original closed form ANCHOR · STEP^(chapter − ANCHOR_CH). A tapering early-chapter bump (see [enemy_early_chapter_mult]) multiplies the result.
enemy_power_scalar
Enemy “power” scalar for a fight — the value spawn_wave feeds into the HP/attack curve, and the apples-to-apples counterpart of the player’s character.power (both normalized to BASE_POWER). For campaign fights: at/below the anchor chapter it is the hand-authored FightTemplate.power (base_power); above it the rebalanced geometric curve (enemy_power_for_chapter, which carries the sweepable anchor/step + the early-chapter bump), with a boss bump. For DUNGEON fights it is always the authored per-difficulty base_power (see below). Shared so the battle-end analytics report exactly the value the fight spawned.
get_attr_from_attrs
attrs.get_attr(name): read the composed attribute value from an attribute map. Returns (base + bonus) * (1 + bonus / 10000.0).
hp_k_for_chapter
hp_k_for_level
init_tuning
Set the process-wide balance tuning once at startup (first call wins).
item_q_by_day
level_by_day
mean_fight_duration
power_from_attrs
Compute character power from a composed AttrMap as P = DPS × EHP (balance v2). DPS is the offensive product (attack · rate · crit · multicast · bravery · counterattack); EHP the survivability product (hp ÷ the damage that gets through), where armor and dodge use the diminishing-returns curves armor_k and ev/(ev+K_DODGE). Normalised by POWER_NORM so a reference character (attack=BASE_ATTACK, hp=BASE_HP, neutral elsewhere) anchors at BASE_POWER. Replaces the legacy opaque -style formula; every factor maps 1:1 to the old one except armor and evasion, which move from a linear cap to a DR curve.
rand_round_f64
Pure: stochastic round of a non-integer value.
sell_price
Item sale price (sell-currency units) from an item’s effectiveness eff: floor(eff^sell_price_exp · sell_price_coef). Sub-linear (exp < 1) so gold income grows ~linearly with item level and the geometric chest sink can bind (Phase 2). Pure + sim-tunable via tuning(); the call site is behaviors::items::item_price.
tuning
The process-wide balance tuning (defaults until init_tuning is called). Hot-path safe — an atomic load of a &'static.

Type Aliases§

AttrMap
Ordered attribute accumulator used by power_from_attrs / character_power.