M6.18: Skill expansion to 5 per ability — 12 new Theriapolis-flavored skills

Skill list grows from 18 → 30 so every ability has 5 skills tied to it.
New skills appended at SkillId byte indices 18–29 to preserve
save-game compat with pre-M6.18 characters.

  STR (+4): Brawl, Build-Read, Force, Haulage
  DEX (+2): Driving, Marksmanship
  CON (+5): Endurance, Fortitude, Hardiness, Lung-Craft, Pain Tolerance
  CHA (+1): Scent-Speak

Endurance covers applied effort over time; Hardiness covers external
condition tolerance (temperature, smoke, altitude); Fortitude is
ingestion-only (poison, spoiled food, ritual draughts). Pain Tolerance
is function-while-wounded. Lung-Craft is breath/voice discipline.

Class skill_options updated across all 8 callings so every new skill
has 3+ class homes (Muzzle-Speaker remains the universal pick of
all 30). Backgrounds left untouched — their skill grants are
doc-canon and would benefit from a separate balancing pass.

CharacterBuilder.SkillToJsonName extended with explicit cases for the
new skills so compound enum names like BuildRead serialize as
"build_read" rather than the fallback "buildread" — caught by
CharacterBuilderTests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Wiebe
2026-05-09 20:25:56 -07:00
parent 39117a09ed
commit 97b49d4145
4 changed files with 95 additions and 15 deletions
+8 -8
View File
@@ -10,7 +10,7 @@
"weapon_proficiencies": ["simple", "martial", "natural"],
"tool_proficiencies": [],
"skills_choose": 2,
"skill_options": ["athletics", "intimidation", "perception", "survival", "animal_handling"],
"skill_options": ["athletics", "animal_handling", "brawl", "force", "intimidation", "marksmanship", "pain_tolerance", "perception", "survival"],
"subclass_ids": ["pack_forged", "lone_fang"],
"starting_kit": [
{ "item_id": "rend_sword", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -68,7 +68,7 @@
"weapon_proficiencies": ["simple", "martial", "natural"],
"tool_proficiencies": [],
"skills_choose": 2,
"skill_options": ["athletics", "insight", "intimidation", "medicine", "perception"],
"skill_options": ["athletics", "endurance", "fortitude", "hardiness", "insight", "intimidation", "medicine", "pain_tolerance", "perception"],
"subclass_ids": ["herd_wall", "antler_guard"],
"starting_kit": [
{ "item_id": "hoof_club", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -128,7 +128,7 @@
"weapon_proficiencies": ["simple", "natural"],
"tool_proficiencies": [],
"skills_choose": 2,
"skill_options": ["athletics", "intimidation", "nature", "perception", "survival"],
"skill_options": ["athletics", "brawl", "endurance", "fortitude", "hardiness", "haulage", "intimidation", "nature", "perception", "survival"],
"subclass_ids": ["blood_memory", "stampede_heart"],
"starting_kit": [
{ "item_id": "paw_axe", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -191,7 +191,7 @@
"weapon_proficiencies": ["simple", "hand_crossbow", "short_sword", "rapier", "natural"],
"tool_proficiencies": ["thieves_tools"],
"skills_choose": 4,
"skill_options": ["acrobatics", "athletics", "deception", "insight", "intimidation", "investigation", "perception", "persuasion", "sleight_of_hand", "stealth"],
"skill_options": ["acrobatics", "athletics", "build_read", "deception", "driving", "insight", "intimidation", "investigation", "marksmanship", "perception", "persuasion", "sleight_of_hand", "stealth"],
"subclass_ids": ["noseblind", "ambush_artist"],
"starting_kit": [
{ "item_id": "thorn_blade", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -255,7 +255,7 @@
"weapon_proficiencies": ["simple", "natural"],
"tool_proficiencies": ["alchemists_supplies", "perfumers_kit"],
"skills_choose": 3,
"skill_options": ["deception", "insight", "investigation", "medicine", "perception", "persuasion", "stealth"],
"skill_options": ["build_read", "deception", "insight", "investigation", "lung_craft", "medicine", "perception", "persuasion", "scent_speak", "stealth"],
"subclass_ids": ["perfumer", "tracker"],
"starting_kit": [
{ "item_id": "fang_knife", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -316,7 +316,7 @@
"weapon_proficiencies": ["simple", "martial", "natural"],
"tool_proficiencies": [],
"skills_choose": 2,
"skill_options": ["athletics", "insight", "intimidation", "medicine", "persuasion", "religion"],
"skill_options": ["athletics", "brawl", "force", "insight", "intimidation", "lung_craft", "medicine", "persuasion", "religion", "scent_speak"],
"subclass_ids": ["the_warden", "the_bridge"],
"starting_kit": [
{ "item_id": "rend_sword", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -377,7 +377,7 @@
"weapon_proficiencies": ["simple", "natural"],
"tool_proficiencies": ["musical_instrument", "musical_instrument_2", "musical_instrument_3"],
"skills_choose": 3,
"skill_options": ["acrobatics", "animal_handling", "arcana", "athletics", "deception", "history", "insight", "intimidation", "investigation", "medicine", "nature", "perception", "performance", "persuasion", "religion", "sleight_of_hand", "stealth", "survival"],
"skill_options": ["acrobatics", "animal_handling", "arcana", "athletics", "brawl", "build_read", "deception", "driving", "endurance", "force", "fortitude", "hardiness", "haulage", "history", "insight", "intimidation", "investigation", "lung_craft", "marksmanship", "medicine", "nature", "pain_tolerance", "perception", "performance", "persuasion", "religion", "scent_speak", "sleight_of_hand", "stealth", "survival"],
"subclass_ids": ["warhorn", "whisperfur"],
"starting_kit": [
{ "item_id": "fang_knife", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -440,7 +440,7 @@
"weapon_proficiencies": ["simple", "natural", "firearms"],
"tool_proficiencies": ["tinkers_tools", "artisans_tools", "artisans_tools_2"],
"skills_choose": 3,
"skill_options": ["arcana", "investigation", "medicine", "nature", "perception", "sleight_of_hand"],
"skill_options": ["arcana", "build_read", "driving", "force", "haulage", "investigation", "medicine", "nature", "perception", "sleight_of_hand"],
"subclass_ids": ["combat_engineer", "body_wright"],
"starting_kit": [
{ "item_id": "hoof_club", "qty": 1, "auto_equip": true, "equip_slot": "main_hand" },
@@ -401,6 +401,19 @@ public sealed class CharacterBuilder
SkillId.SleightOfHand => "sleight_of_hand",
SkillId.Stealth => "stealth",
SkillId.Survival => "survival",
SkillId.Brawl => "brawl",
SkillId.BuildRead => "build_read",
SkillId.Driving => "driving",
SkillId.Endurance => "endurance",
SkillId.Force => "force",
SkillId.Fortitude => "fortitude",
SkillId.Hardiness => "hardiness",
SkillId.Haulage => "haulage",
SkillId.LungCraft => "lung_craft",
SkillId.Marksmanship => "marksmanship",
SkillId.PainTolerance => "pain_tolerance",
SkillId.ScentSpeak => "scent_speak",
_ => s.ToString().ToLowerInvariant(),
};
+45 -2
View File
@@ -1,8 +1,11 @@
namespace Theriapolis.Core.Rules.Stats;
/// <summary>
/// Standard d20-adjacent skill list. Each skill is backed by a single
/// ability — see <see cref="SkillAbility"/>.
/// Skill list used by Theriapolis — extends the d20 baseline with 12
/// additional skills (M6.18) so each ability has 5 skills tied to it.
/// New skills appended at the end of the byte enum to preserve save-game
/// compatibility with characters created before M6.18. Each skill is
/// backed by a single ability — see <see cref="SkillIdExtensions.Ability"/>.
/// </summary>
public enum SkillId : byte
{
@@ -24,6 +27,20 @@ public enum SkillId : byte
SleightOfHand = 15,
Stealth = 16,
Survival = 17,
// M6.18 — Theriapolis-flavored expansions, 5 per ability.
Brawl = 18, // STR
BuildRead = 19, // STR
Driving = 20, // DEX
Endurance = 21, // CON
Force = 22, // STR
Fortitude = 23, // CON
Hardiness = 24, // CON
Haulage = 25, // STR
LungCraft = 26, // CON
Marksmanship = 27, // DEX
PainTolerance = 28, // CON
ScentSpeak = 29, // CHA
}
public static class SkillIdExtensions
@@ -48,6 +65,19 @@ public static class SkillIdExtensions
SkillId.SleightOfHand => AbilityId.DEX,
SkillId.Stealth => AbilityId.DEX,
SkillId.Survival => AbilityId.WIS,
SkillId.Brawl => AbilityId.STR,
SkillId.BuildRead => AbilityId.STR,
SkillId.Driving => AbilityId.DEX,
SkillId.Endurance => AbilityId.CON,
SkillId.Force => AbilityId.STR,
SkillId.Fortitude => AbilityId.CON,
SkillId.Hardiness => AbilityId.CON,
SkillId.Haulage => AbilityId.STR,
SkillId.LungCraft => AbilityId.CON,
SkillId.Marksmanship => AbilityId.DEX,
SkillId.PainTolerance => AbilityId.CON,
SkillId.ScentSpeak => AbilityId.CHA,
_ => throw new ArgumentOutOfRangeException(nameof(s)),
};
@@ -72,6 +102,19 @@ public static class SkillIdExtensions
"sleight_of_hand" => SkillId.SleightOfHand,
"stealth" => SkillId.Stealth,
"survival" => SkillId.Survival,
"brawl" => SkillId.Brawl,
"build_read" => SkillId.BuildRead,
"driving" => SkillId.Driving,
"endurance" => SkillId.Endurance,
"force" => SkillId.Force,
"fortitude" => SkillId.Fortitude,
"hardiness" => SkillId.Hardiness,
"haulage" => SkillId.Haulage,
"lung_craft" => SkillId.LungCraft,
"marksmanship" => SkillId.Marksmanship,
"pain_tolerance" => SkillId.PainTolerance,
"scent_speak" => SkillId.ScentSpeak,
_ => throw new ArgumentException($"Unknown skill: '{raw}'"),
};
}
+29 -5
View File
@@ -1,15 +1,15 @@
namespace Theriapolis.GodotHost.UI;
/// <summary>
/// All 18 skills with display labels, governing ability, and codex
/// All 30 skills with display labels, governing ability, and codex
/// flavor descriptions. Entries are keyed by their snake_case JSON id —
/// the same string that appears in <c>class.skill_options</c> and
/// <c>background.skill_proficiencies</c> in <c>Content/Data</c>.
///
/// Labels and ability mapping mirror Theriapolis.Core.Rules.Stats.SkillId;
/// descriptions are ported verbatim from <c>src/data.jsx</c>'s
/// <c>SKILL_DESC</c> table in the React prototype. If the JSON schema
/// gains a description field later, swap to a data-driven lookup.
/// Labels and ability mapping mirror Theriapolis.Core.Rules.Stats.SkillId.
/// The 18 d20-baseline descriptions are ported from the React prototype's
/// <c>SKILL_DESC</c> table; the 12 M6.18 expansions (5 per ability total)
/// are Theriapolis-specific and authored against the design canon.
/// </summary>
public static class SkillsCatalog
{
@@ -25,8 +25,24 @@ public static class SkillsCatalog
"Knowledge of the older magics: scent-sorcery, blood-sigil, the half-forgotten rites that pre-date the Covenant of Claws."),
new("athletics", "Athletics", "STR",
"Raw physical effort. Climbing scaffold, swimming the foul canal, hauling a packmate from the pit, breaking a hold."),
new("brawl", "Brawl", "STR",
"Bare-fanged unarmed violence — claws, teeth, horns, hooves. The coliseum tradition and the back-alley one. Distinct from Athletics (the running body) and Intimidation (the threat unspoken) — this is the application of natural-weapon force."),
new("build_read", "Build-Read", "STR",
"Sizing up another creature's physical capabilities at a glance. Cross-clade body diversity makes this nontrivial — a Mustelid's frame lies about its strength, a Cervid's stillness lies about its speed. The dockside boxer's eye for who can fight."),
new("deception", "Deception", "CHA",
"Speaking convincingly past your scent. The art of the false posture, the planted rumor, the answer that is technically true."),
new("driving", "Driving", "DEX",
"Vehicle and mount control under pressure. Caravan-work, Hare-courier carts, riding a clademorphic beast over broken ground, navigating a contested street at speed."),
new("endurance", "Endurance", "CON",
"Sustained applied effort. The act of doing hard work for a long time — forced marches, all-night hauls, rowing the canal-shift, the long chase. What you put out, hour after hour."),
new("force", "Force", "STR",
"Applied violence on objects. Doors, chains, shutters, locked chests, walls that shouldn't be there anymore. The Bulwark's quiet talent and the Claw-Wright's last resort."),
new("fortitude", "Fortitude", "CON",
"Resisting what you swallow. Ingested poison and contagion, spoiled rations, foreign cuisine, the Goat-Folk welcome of rotted cheese, the dockside whiskey flight, the ritual draught you cannot politely refuse."),
new("hardiness", "Hardiness", "CON",
"Withstanding external conditions. Temperature extremes, smoke and dust, thin mountain air, the slow attrition of weather. The Polar Bear-Folk's cold tolerance, the Coyote-Folk's heat-of-the-day shift, the herd-wall holding under blizzard."),
new("haulage", "Haulage", "STR",
"Bearing weight under distance. Porter discipline, stretcher-craft, shouldering an unconscious packmate up a switchback. The Bovid wagon-train's daily reality and the battlefield medic's quiet requirement."),
new("history", "History", "INT",
"The long memory of Theriapolis — the Imperium's fall, the Compact's ratification, which clade owes which other a centuries-old debt."),
new("insight", "Insight", "WIS",
@@ -35,10 +51,16 @@ public static class SkillsCatalog
"Bared-teeth diplomacy. The threat made plain enough that violence is not required to extract compliance."),
new("investigation", "Investigation", "INT",
"Methodical search and inference: scene-reading, document-sifting, the patient accumulation of small facts into a verdict."),
new("lung_craft", "Lung-Craft", "CON",
"Sustained vocalization and breath-control. The physical basis of Muzzle-Speaker oratory, the diver's stillness underwater, the smoke-tolerant veteran's projection across a coliseum without shredding the throat."),
new("marksmanship", "Marksmanship", "DEX",
"Accurate ranged weapon use — bow, crossbow, sling, javelin, thrown blade. The skill that lets prey-clade militias hold a wall against predator charge, and the Shadow-Pelt's clean answer at distance."),
new("medicine", "Medicine", "WIS",
"Field surgery, poultice-craft, knowing which clade tolerates which tincture. Stabilizing the dying without finishing them."),
new("nature", "Nature", "INT",
"Knowledge of the wild outside the city wall — terrain, weather, plant-lore, and the unsigned beasts that observe no Covenant."),
new("pain_tolerance", "Pain Tolerance", "CON",
"Function while wounded. Holding the line bleeding, finishing the sentence with a knife in your shoulder, performing surgery on yourself. The hybrid medical-mistrust skill — the one you use when the doctor isn't an option."),
new("perception", "Perception", "WIS",
"Awareness through every sense your clade gives you: ear-cock, scent-prickle, the half-glimpsed shape at the edge of vision."),
new("performance", "Performance", "CHA",
@@ -47,6 +69,8 @@ public static class SkillsCatalog
"Open-handed argument. The case made on its merits, the appeal to mutual benefit, the patient construction of agreement."),
new("religion", "Religion", "INT",
"The hymn-cycles of the Cervid liturgy, the Compact's sacred clauses, the small household rites your clade keeps without thinking."),
new("scent_speak", "Scent-Speak", "CHA",
"Deliberate pheromone communication — the unspoken half of every Theriapolis conversation. Broadcasting calm, threat, deference, lineage, trust. The Scent-Broker's craft, the passer's nightmare, the perfumer's living. Distinct from Insight (which reads the scent) — this is the active emission."),
new("sleight_of_hand", "Sleight of Hand", "DEX",
"Quiet fingers — pickpocketing, palmed coins, the swap performed under another's nose. Useful in markets and courtrooms alike."),
new("stealth", "Stealth", "DEX",