Character Leveling
This page explains how the rpgleveler engine processes a character leveling up.
Overview
Leveling up is implemented as a pure transformation:
- The original character is not modified
- A new Character object is returned
- A LevelUpResult summarizes what changed
This makes the system:
- Easy to test
- Predictable
- Safe to use in pipelines or simulations
Level-Up Flow
When a character is leveled up, the engine performs the following steps:
- Validate eligibility
- Determine next level
- Roll hit point gain
- Retrieve progression data
- Build updated character
- Return result summary
Step-by-Step Breakdown
1. Validation
The engine checks:
- The character is below max level (20)
- The character has enough XP for the next level
can_level_up(character)
If not, a LevelUpError is raised.
2. Determine Next Level
new_level = character.level + 1
3. Hit Point Gain
Hit points are calculated using:
- Class hit dice
- Constitution modifier
- Racial caps (Elf/Halfling → max d6)
- Fixed gains after level 9
hp_gained = roll_hp_gain(character, rng)
Rules:
- Minimum gain is always 1 HP
- CON modifier applies only before level 10
4. Progression Updates
The engine retrieves updated values from data tables:
get_attack_bonus(...)
get_saving_throws(...)
get_spell_slots(...)
get_thief_skills(...)
get_turn_undead(...)
Important Design Choice
All classes receive all data structures, even if unused:
- Fighters still have
spell_slots(all zeros) - Clerics still have
thief_skills(all zeros) - Non-clerics have
turn_undead(allNone)
This avoids conditional logic and keeps the system uniform.
5. Build New Character
A new character is created using:
dataclasses.replace(...)
Only level-dependent fields are updated:
levelhpattack_bonussaving_throwsspell_slotsthief_skillsturn_undead
Everything else remains unchanged.
6. Result Summary
A LevelUpResult object captures:
- Old vs new level
- HP gained
- Updated combat stats
- Updated class features
This is useful for:
- CLI output
- Logging
- UI display
Example
Input Character
{
"class": "fighter",
"level": 1,
"xp": 2200,
"hp": 8
}
After Level-Up
{
"level": 2,
"hp": 13,
"attack_bonus": 1
}
Result Summary
LevelUpResult(
old_level=1,
new_level=2,
hp_gained=5,
new_hp_total=13,
...
)
Deterministic Leveling (Seeds)
You can control randomness using a seed:
rpgleveler character.json --seed 42
This guarantees:
- Same HP rolls
- Reproducible results
- Stable tests
Design Principles
Pure Functions
- No mutation of input objects
Immutable Data
- Tables use
MappingProxyType
Uniform Data Model
- All classes share the same structure
Separation of Concerns
- XP logic →
advancement.py - Tables →
data/ - HP logic →
hit_points.py - Orchestration →
leveler.py
Why This Matters
This architecture allows you to:
- Chain tools together (your CLI vision 👀)
- Run simulations safely
- Serialize/deserialize easily
- Extend the system without breaking things
TL;DR
Leveling up is:
Validate → Roll HP → Lookup tables → Build new character → Return result
Clean, predictable, and testable.