Initial commit: Theriapolis baseline at port/godot branch point

Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Christopher Wiebe
2026-04-30 20:40:51 -07:00
commit b451f83174
525 changed files with 75786 additions and 0 deletions
+223
View File
@@ -0,0 +1,223 @@
using Theriapolis.Core.Rules.Stats;
namespace Theriapolis.Game.UI;
/// <summary>
/// Author-curated UI text that the wizard surfaces but isn't worth pushing
/// into JSON — skill descriptions, language metadata, ability labels, and
/// the informational class↔clade recommendation table.
///
/// Sourced from the Phase 5 character-creator design handoff
/// (`_design_handoff/character_creation/` snapshot, src/data.jsx). Keeping
/// these here rather than in <c>Content/Data/</c> lets the design tone live
/// next to the code that consumes it.
/// </summary>
public static class CodexCopy
{
/// <summary>STR/DEX/CON/INT/WIS/CHA → long display name.</summary>
public static readonly System.Collections.Generic.IReadOnlyDictionary<AbilityId, string> AbilityLabels =
new System.Collections.Generic.Dictionary<AbilityId, string>
{
[AbilityId.STR] = "Strength",
[AbilityId.DEX] = "Dexterity",
[AbilityId.CON] = "Constitution",
[AbilityId.INT] = "Intellect",
[AbilityId.WIS] = "Wisdom",
[AbilityId.CHA] = "Charisma",
};
/// <summary>SizeCategory snake_case → pretty label.</summary>
public static string SizeLabel(string sizeKey) => sizeKey switch
{
"small" => "Small",
"medium" => "Medium",
"medium_large" => "Medium-Large",
"large" => "Large",
_ => sizeKey,
};
/// <summary>Skill id (snake_case) → display name.</summary>
public static string SkillName(string skillId) => skillId switch
{
"acrobatics" => "Acrobatics",
"animal_handling" => "Animal Handling",
"arcana" => "Arcana",
"athletics" => "Athletics",
"deception" => "Deception",
"history" => "History",
"insight" => "Insight",
"intimidation" => "Intimidation",
"investigation" => "Investigation",
"medicine" => "Medicine",
"nature" => "Nature",
"perception" => "Perception",
"performance" => "Performance",
"persuasion" => "Persuasion",
"religion" => "Religion",
"sleight_of_hand" => "Sleight of Hand",
"stealth" => "Stealth",
"survival" => "Survival",
_ => skillId,
};
/// <summary>One-sentence skill description in the codex's tone (from the design's data.jsx).</summary>
public static string SkillDescription(string skillId) => skillId switch
{
"acrobatics" => "Tumbling, balance, and the kind of footwork that keeps you upright on a coliseum sand-floor or a warren-rope. Body-cunning under pressure.",
"animal_handling" => "Reading and steering non-sentient beasts — feral hounds, draft-kine, the wild cousins of your own clade.",
"arcana" => "Knowledge of the older magics: scent-sorcery, blood-sigil, the half-forgotten rites that pre-date the Covenant of Claws.",
"athletics" => "Raw physical effort. Climbing scaffold, swimming the foul canal, hauling a packmate from the pit, breaking a hold.",
"deception" => "Speaking convincingly past your scent. The art of the false posture, the planted rumor, the answer that is technically true.",
"history" => "The long memory of Theriapolis — the Imperium's fall, the Compact's ratification, which clade owes which other a centuries-old debt.",
"insight" => "Reading another's true posture beneath their words. Catching the off-note in a snarl, the held breath, the lie in a friendly tail.",
"intimidation" => "Bared-teeth diplomacy. The threat made plain enough that violence is not required to extract compliance.",
"investigation" => "Methodical search and inference: scene-reading, document-sifting, the patient accumulation of small facts into a verdict.",
"medicine" => "Field surgery, poultice-craft, knowing which clade tolerates which tincture. Stabilizing the dying without finishing them.",
"nature" => "Knowledge of the wild outside the city wall — terrain, weather, plant-lore, and the unsigned beasts that observe no Covenant.",
"perception" => "Awareness through every sense your clade gives you: ear-cock, scent-prickle, the half-glimpsed shape at the edge of vision.",
"performance" => "Holding an audience — coliseum crowd, courtroom gallery, market square. Song, oratory, the body that compels watching.",
"persuasion" => "Open-handed argument. The case made on its merits, the appeal to mutual benefit, the patient construction of agreement.",
"religion" => "The hymn-cycles of the Cervid liturgy, the Compact's sacred clauses, the small household rites your clade keeps without thinking.",
"sleight_of_hand" => "Quiet fingers — pickpocketing, palmed coins, the swap performed under another's nose.",
"stealth" => "Movement unseen and unsmelled. Wind-checking, scent-suppression, the slow weight-shift on a creaking floor.",
"survival" => "Field-craft beyond the wall: tracking, foraging, fire-making, knowing which run-off is safe to drink.",
_ => "",
};
/// <summary>Skill id → governing ability.</summary>
public static AbilityId SkillAbility(string skillId)
{
try { return SkillIdExtensions.FromJson(skillId).Ability(); }
catch { return AbilityId.STR; }
}
/// <summary>
/// <see cref="SkillId"/> enum → snake_case JSON id. Inverse of
/// <see cref="SkillIdExtensions.FromJson"/>. Required because
/// <c>enum.ToString().ToLowerInvariant()</c> produces "sleightofhand"
/// for <c>SleightOfHand</c>, which doesn't match the JSON-keyed
/// dictionaries (skill name / skill description).
/// </summary>
public static string SkillIdToJson(SkillId s) => s switch
{
SkillId.Acrobatics => "acrobatics",
SkillId.AnimalHandling => "animal_handling",
SkillId.Arcana => "arcana",
SkillId.Athletics => "athletics",
SkillId.Deception => "deception",
SkillId.History => "history",
SkillId.Insight => "insight",
SkillId.Intimidation => "intimidation",
SkillId.Investigation => "investigation",
SkillId.Medicine => "medicine",
SkillId.Nature => "nature",
SkillId.Perception => "perception",
SkillId.Performance => "performance",
SkillId.Persuasion => "persuasion",
SkillId.Religion => "religion",
SkillId.SleightOfHand => "sleight_of_hand",
SkillId.Stealth => "stealth",
SkillId.Survival => "survival",
_ => s.ToString().ToLowerInvariant(),
};
/// <summary>Language id → display name.</summary>
public static string LanguageName(string langId) => langId switch
{
"common" => "Common",
"canid" => "Canid",
"felid" => "Felid",
"mustelid" => "Mustelid",
"ursid" => "Ursid",
"cervid" => "Cervid",
"bovid" => "Bovid",
"leporid" => "Leporid",
_ => langId,
};
/// <summary>Language id → flavor description for hover/detail panels.</summary>
public static string LanguageDescription(string langId) => langId switch
{
"common" => "The market-and-courthouse trade tongue of Theriapolis. Spoken by every clade.",
"canid" => "Pack-tongue of the Canidae. Heavy with subsonic registers and scent-words non-Canid speakers cannot fully parse.",
"felid" => "Sinuous and tonal, with a parallel tail-and-ear pidgin. Felid speakers trade in implication and pause.",
"mustelid" => "Quick, percussive trade-speech of the Mustelidae. Famous for its dense vocabulary of musks, debts, and small grievances.",
"ursid" => "Slow, low-register growl-speech. Ursid grammar prefers final emphasis — the important word always comes last.",
"cervid" => "Old, hymn-shaped tongue of the Cervidae. Most speakers know Cervid as a song-language for funerals, treaties, and the long calendar.",
"bovid" => "Patient, formal speech of the herd-clades. The language of guild-councils and oaths.",
"leporid" => "Rapid, twitch-paced chatter of the Leporidae. Uses tense markers for danger and runs faster than most non-Leporidae can follow.",
_ => "",
};
/// <summary>Item id → pretty display name (mirrors items.json's <c>name</c> for the subset used by starting kits).</summary>
public static string ItemName(string itemId) => itemId switch
{
"rend_sword" => "Rend-sword",
"chain_shirt" => "Chain Shirt",
"buckler" => "Buckler",
"healers_kit" => "Healer's Kit",
"rations_predator" => "Rations (predator)",
"rations_prey" => "Rations (prey)",
"hoof_club" => "Hoof Club",
"chain_mail" => "Chain Mail",
"standard_shield" => "Standard Shield",
"paw_axe" => "Paw-axe",
"hide_vest" => "Hide Vest",
"thorn_blade" => "Thorn-blade",
"studded_leather" => "Studded Leather",
"claw_bow" => "Claw-bow",
"poultice_universal" => "Universal Poultice",
"scent_mask_basic" => "Basic Scent-mask",
"fang_knife" => "Fang-knife",
"leather_harness" => "Leather Harness",
"pheromone_vial_calm" => "Pheromone Vial (calm)",
"pheromone_vial_fear" => "Pheromone Vial (fear)",
"rope_claw_braid" => "Claw-braid Rope",
_ => itemId,
};
/// <summary>
/// Class id → list of clade ids that suit it. Drives the "★ Suits Clade"
/// recommendation badge on the calling step. Informational only — the
/// player can still pick any class with any clade.
/// </summary>
public static readonly System.Collections.Generic.IReadOnlyDictionary<string, string[]> ClassCladeRecommendations =
new System.Collections.Generic.Dictionary<string, string[]>
{
["fangsworn"] = new[] { "canidae", "felidae", "ursidae" },
["bulwark"] = new[] { "bovidae", "ursidae" },
["feral"] = new[] { "ursidae", "mustelidae", "bovidae" },
["shadow_pelt"] = new[] { "felidae", "mustelidae", "leporidae" },
["scent_broker"] = new[] { "canidae", "mustelidae" },
["covenant_keeper"] = new[] { "canidae", "bovidae", "cervidae" },
["muzzle_speaker"] = new[] { "felidae", "leporidae" },
["claw_wright"] = new[] { "mustelidae", "leporidae" },
};
/// <summary>Returns true if <paramref name="cladeId"/> is one of the recommended clades for <paramref name="classId"/>.</summary>
public static bool IsSuited(string classId, string cladeId)
{
if (!ClassCladeRecommendations.TryGetValue(classId, out var clades)) return false;
foreach (var c in clades)
if (string.Equals(c, cladeId, System.StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
/// <summary>STR/DEX/.../CHA in canonical order (matches the design's `ABILITIES`).</summary>
public static readonly AbilityId[] AbilityOrder = new[]
{
AbilityId.STR, AbilityId.DEX, AbilityId.CON, AbilityId.INT, AbilityId.WIS, AbilityId.CHA,
};
/// <summary>Roman numeral 1..7 for stepper labels ("Folio I of VII").</summary>
public static string Romanize(int n) => n switch
{
1 => "I", 2 => "II", 3 => "III", 4 => "IV",
5 => "V", 6 => "VI", 7 => "VII", 8 => "VIII",
_ => n.ToString(),
};
/// <summary>"+N" for n &gt;= 0, "-N" otherwise.</summary>
public static string Signed(int n) => n >= 0 ? $"+{n}" : n.ToString();
}