osrlib.core.abilities
Ability scores: modifier tables, checks, prime requisites, and the adjustment step.
The six modifier tables (and the prime requisite XP table) are compiled from the SRD's
Ability Scores page into abilities.json and load as one frozen
AbilityTables model, which carries one accessor
per SRD column. Tables are stored as score bands exactly as the SRD prints them (4–5,
13–15), validated to cover 3–18 contiguously.
Scores range 3–18: 3d6 can roll nothing else, the SRD's tables list nothing else, and the creation-time adjustment step may not raise a score above 18 nor lower one below 9.
Ability checks per the SRD: roll 1d20, equal-or-under the score succeeds, with a caller-supplied difficulty modifier (±4 easy/hard) applied to the roll. A natural 1 always succeeds and a natural 20 always fails — inverted from attack rolls, where a natural 20 always hits. Open-doors checks are d6 ≤ the STR-derived chance.
The adjustment step is pure validation plus atomic application over a rolled score set and a chosen class's prime requisites: lower STR/INT/WIS two-for-one into prime requisites, floor 9, cap 18, with class-specific restrictions (a thief may not lower STR) carried as data on the class definition.
ADJUSTMENT_FLOOR
module-attribute
No score may be lowered below this value during the adjustment step.
MAX_SCORE
module-attribute
The highest possible ability score (3d6 maximum; also the adjustment raise cap).
MIN_SCORE
module-attribute
The lowest possible ability score (3d6 minimum; also the tables' floor).
AbilityAdjustment
Bases: BaseModel
The creation-time adjustment: even reductions traded two-for-one into prime requisite raises.
lowered maps abilities to the (positive) amount subtracted; raised maps
abilities to the (positive) amount added. An empty adjustment is a legal no-op.
AbilityCheckResult
AbilityScore
Bases: StrEnum
The six ability scores.
The wire values are lowercase ("str", "int", ...) — they serialize into
characters and saves; changing them is a schema_version bump.
AbilityTables
Bases: BaseModel
The six ability modifier tables plus the prime requisite XP table.
Loaded from abilities.json via
load_ability_tables. Accessors take a score in
3–18 and raise stdlib ValueError outside that range (programmer misuse).
melee_modifier
Return the STR modifier to melee attack and damage rolls.
open_doors_chance
Return the STR-derived X-in-6 chance to force open a stuck door.
additional_languages
Return the number of additional spoken languages granted by INT.
literacy
Return the INT-derived literacy in the character's native languages.
magic_save_modifier
Return the WIS modifier to saving throws versus magical effects.
ac_modifier
Return the DEX modifier to AC (a bonus lowers descending AC).
missile_modifier
Return the DEX modifier to missile attack rolls (not damage).
initiative_modifier
Return the DEX modifier to individual initiative (optional rule).
hit_point_modifier
Return the CON modifier applied per Hit Die rolled (minimum 1 hp per die).
npc_reaction_modifier
Return the CHA modifier to NPC reactions.
retainer_loyalty
Return the CHA-derived retainer loyalty rating.
CharismaRow
Bases: ScoreBand
One CHA band: NPC reaction modifier, retainer maximum, and retainer loyalty.
ConstitutionRow
DexterityRow
IntelligenceRow
Bases: ScoreBand
One INT band: additional spoken languages, literacy, and broken speech at INT 3.
Literacy
Bases: StrEnum
The INT table's literacy column.
OpenDoorsResult
PrimeRequisiteRow
ScoreBand
StrengthRow
WisdomRow
ability_check
ability_check(score: int, stream: RngStream, modifier: int = 0) -> AbilityCheckResult
Roll an ability check: 1d20, equal-or-under the score succeeds.
The caller-supplied difficulty modifier is added to the roll (the SRD suggests −4 for an easy task, +4 for a difficult one). A natural 1 always succeeds and a natural 20 always fails — inverted from attack rolls, where a natural 20 always hits and a natural 1 always misses.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
score
|
int
|
The ability score to check against, in 3–18. |
required |
stream
|
RngStream
|
The RNG stream to draw from. |
required |
modifier
|
int
|
Difficulty modifier added to the roll; positive makes it harder. |
0
|
Returns:
| Type | Description |
|---|---|
AbilityCheckResult
|
The check outcome, including the raw d20 roll. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
apply_adjustment
apply_adjustment(
scores: dict[AbilityScore, int],
adjustment: AbilityAdjustment,
prime_requisites: tuple[AbilityScore, ...],
may_not_lower: tuple[AbilityScore, ...] = (),
) -> dict[AbilityScore, int]
Apply a validated adjustment atomically, returning the new score set.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scores
|
dict[AbilityScore, int]
|
The rolled scores; not mutated. |
required |
adjustment
|
AbilityAdjustment
|
The adjustment to apply. |
required |
prime_requisites
|
tuple[AbilityScore, ...]
|
The chosen class's prime requisites. |
required |
may_not_lower
|
tuple[AbilityScore, ...]
|
Class-specific lowering restrictions. |
()
|
Returns:
| Type | Description |
|---|---|
dict[AbilityScore, int]
|
A new score dict with reductions and raises applied. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the adjustment fails
|
open_doors_check
open_doors_check(chance: int, stream: RngStream) -> OpenDoorsResult
Roll an open-doors check: d6, equal-or-under the X-in-6 chance succeeds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
chance
|
int
|
The X-in-6 chance, from
|
required |
stream
|
RngStream
|
The RNG stream to draw from. |
required |
Returns:
| Type | Description |
|---|---|
OpenDoorsResult
|
The check outcome, including the raw d6 roll. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
validate_adjustment
validate_adjustment(
scores: dict[AbilityScore, int],
adjustment: AbilityAdjustment,
prime_requisites: tuple[AbilityScore, ...],
may_not_lower: tuple[AbilityScore, ...] = (),
) -> list[Rejection]
Validate an adjustment against the SRD's rules, returning structured rejections.
The rules, per the SRD's Creating a Character step 3 and the adaptations register's interpretations:
- Only STR, INT, and WIS may be lowered.
- A prime requisite of the chosen class may not be lowered, nor may an ability in
the class's
may_not_lowerrestrictions (the thief's STR). - Each lowered score drops by an even amount (the two-for-one trade is per-score, so an odd reduction would strand half a point).
- The total raise equals the sum of reductions divided by two, distributed freely among the class's prime requisites and nowhere else.
- No lowered score drops below 9; no raised score rises above 18.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scores
|
dict[AbilityScore, int]
|
The rolled scores, all six abilities present. |
required |
adjustment
|
AbilityAdjustment
|
The proposed adjustment. |
required |
prime_requisites
|
tuple[AbilityScore, ...]
|
The chosen class's prime requisites. |
required |
may_not_lower
|
tuple[AbilityScore, ...]
|
Class-specific lowering restrictions, from the class definition. |
()
|
Returns:
| Type | Description |
|---|---|
list[Rejection]
|
Structured rejections; empty when the adjustment is legal. |