Skip to content

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

ADJUSTMENT_FLOOR = 9

No score may be lowered below this value during the adjustment step.

MAX_SCORE module-attribute

MAX_SCORE = 18

The highest possible ability score (3d6 maximum; also the adjustment raise cap).

MIN_SCORE module-attribute

MIN_SCORE = 3

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.

lowered class-attribute instance-attribute

lowered: dict[AbilityScore, int] = {}

raised class-attribute instance-attribute

raised: dict[AbilityScore, int] = {}

AbilityCheckResult

Bases: BaseModel

The outcome of an ability check, with the raw roll kept for display.

roll instance-attribute

roll: int

score instance-attribute

score: int

modifier instance-attribute

modifier: int

success instance-attribute

success: bool

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.

STR class-attribute instance-attribute

STR = 'str'

INT class-attribute instance-attribute

INT = 'int'

WIS class-attribute instance-attribute

WIS = 'wis'

DEX class-attribute instance-attribute

DEX = 'dex'

CON class-attribute instance-attribute

CON = 'con'

CHA class-attribute instance-attribute

CHA = 'cha'

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).

strength instance-attribute

strength: tuple[StrengthRow, ...]

intelligence instance-attribute

intelligence: tuple[IntelligenceRow, ...]

wisdom instance-attribute

wisdom: tuple[WisdomRow, ...]

dexterity instance-attribute

dexterity: tuple[DexterityRow, ...]

constitution instance-attribute

constitution: tuple[ConstitutionRow, ...]

charisma instance-attribute

charisma: tuple[CharismaRow, ...]

prime_requisite instance-attribute

prime_requisite: tuple[PrimeRequisiteRow, ...]

melee_modifier

melee_modifier(score: int) -> int

Return the STR modifier to melee attack and damage rolls.

open_doors_chance

open_doors_chance(score: int) -> int

Return the STR-derived X-in-6 chance to force open a stuck door.

additional_languages

additional_languages(score: int) -> int

Return the number of additional spoken languages granted by INT.

literacy

literacy(score: int) -> Literacy

Return the INT-derived literacy in the character's native languages.

magic_save_modifier

magic_save_modifier(score: int) -> int

Return the WIS modifier to saving throws versus magical effects.

ac_modifier

ac_modifier(score: int) -> int

Return the DEX modifier to AC (a bonus lowers descending AC).

missile_modifier

missile_modifier(score: int) -> int

Return the DEX modifier to missile attack rolls (not damage).

initiative_modifier

initiative_modifier(score: int) -> int

Return the DEX modifier to individual initiative (optional rule).

hit_point_modifier

hit_point_modifier(score: int) -> int

Return the CON modifier applied per Hit Die rolled (minimum 1 hp per die).

npc_reaction_modifier

npc_reaction_modifier(score: int) -> int

Return the CHA modifier to NPC reactions.

max_retainers

max_retainers(score: int) -> int

Return the CHA-derived maximum number of retainers.

retainer_loyalty

retainer_loyalty(score: int) -> int

Return the CHA-derived retainer loyalty rating.

prime_requisite_xp_modifier_pct

prime_requisite_xp_modifier_pct(score: int) -> int

Return the XP modifier percentage for a single prime requisite at score.

CharismaRow

Bases: ScoreBand

One CHA band: NPC reaction modifier, retainer maximum, and retainer loyalty.

npc_reactions instance-attribute

npc_reactions: int

max_retainers class-attribute instance-attribute

max_retainers: int = Field(ge=0)

retainer_loyalty class-attribute instance-attribute

retainer_loyalty: int = Field(ge=0)

ConstitutionRow

Bases: ScoreBand

One CON band: hit point modifier per Hit Die.

hit_points instance-attribute

hit_points: int

DexterityRow

Bases: ScoreBand

One DEX band: AC modifier, missile attack modifier, and optional-rule initiative modifier.

ac instance-attribute

ac: int

missile instance-attribute

missile: int

initiative instance-attribute

initiative: int

IntelligenceRow

Bases: ScoreBand

One INT band: additional spoken languages, literacy, and broken speech at INT 3.

additional_languages class-attribute instance-attribute

additional_languages: int = Field(ge=0)

literacy instance-attribute

literacy: Literacy

broken_speech class-attribute instance-attribute

broken_speech: bool = False

Literacy

Bases: StrEnum

The INT table's literacy column.

ILLITERATE class-attribute instance-attribute

ILLITERATE = 'illiterate'

BASIC class-attribute instance-attribute

BASIC = 'basic'

LITERATE class-attribute instance-attribute

LITERATE = 'literate'

OpenDoorsResult

Bases: BaseModel

The outcome of an open-doors check, with the raw roll kept for display.

roll instance-attribute

roll: int

chance instance-attribute

chance: int

success instance-attribute

success: bool

PrimeRequisiteRow

Bases: ScoreBand

One prime requisite band: XP modifier percentage for single-prime-requisite classes.

xp_modifier_pct instance-attribute

xp_modifier_pct: int

ScoreBand

Bases: BaseModel

A contiguous score range in a modifier table, as the SRD prints it (4–5).

min_score class-attribute instance-attribute

min_score: int = Field(ge=MIN_SCORE, le=MAX_SCORE)

max_score class-attribute instance-attribute

max_score: int = Field(ge=MIN_SCORE, le=MAX_SCORE)

StrengthRow

Bases: ScoreBand

One STR band: melee modifier and open-doors chance (X-in-6).

melee instance-attribute

melee: int

open_doors class-attribute instance-attribute

open_doors: int = Field(ge=0, le=6)

WisdomRow

Bases: ScoreBand

One WIS band: saving throw modifier versus magical effects.

magic_saves instance-attribute

magic_saves: int

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 score is outside 3–18.

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 validate_adjustment — applying an illegal adjustment is programmer misuse.

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 AbilityTables.open_doors_chance.

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 chance is outside 0–6.

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_lower restrictions (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.