Files
TheriapolisV3/Theriapolis.Core/Rules/Character/HybridState.cs
T
Christopher Wiebe b451f83174 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>
2026-04-30 20:40:51 -07:00

118 lines
4.6 KiB
C#

using Theriapolis.Core.Data;
namespace Theriapolis.Core.Rules.Character;
/// <summary>
/// Phase 6.5 M4 — runtime state for a hybrid character.
///
/// Hybrids are blended from two parent lineages: a <b>Sire</b> (paternal
/// lineage) and a <b>Dam</b> (maternal lineage), of two different clades.
/// One parent is <see cref="DominantParent"/> — the lineage whose physical
/// expression is more visible; this drives Passing eligibility and which
/// clade the PC presents as for casual scent reads.
///
/// The character's own gender is independent of which parent is sire or
/// dam — a male hybrid PC can have a wolf-folk dam and a coyote-folk
/// sire just as readily as the reverse.
///
/// Per <c>theriapolis-rpg-clades.md</c> HYBRID ORIGIN: blend ability mods
/// (one from each parent clade), traits (2 from dominant + 1 from
/// secondary), and inherit *all* universal hybrid detriments — Scent
/// Dysphoria, Illegible Body Language, Social Stigma, Medical
/// Incompatibility (handled by <see cref="HybridDetriments"/>).
///
/// <see cref="PassingActive"/> is toggle-able mid-game; when set, the PC
/// presents as the dominant lineage's clade. Detection by NPCs (Phase 6.5
/// M5) is per-NPC and permanent once revealed.
/// </summary>
public sealed class HybridState
{
/// <summary>Sire (paternal-lineage) clade id, e.g. "canidae".</summary>
public string SireClade { get; init; } = "";
/// <summary>Sire (paternal-lineage) species id, e.g. "wolf".</summary>
public string SireSpecies { get; init; } = "";
/// <summary>Dam (maternal-lineage) clade id, e.g. "leporidae".</summary>
public string DamClade { get; init; } = "";
/// <summary>Dam (maternal-lineage) species id, e.g. "rabbit".</summary>
public string DamSpecies { get; init; } = "";
/// <summary>
/// Which parent's expression is dominant. Drives Passing presentation
/// (the PC scent-reads as this parent's clade) and the trait-split:
/// 2 Clade traits from the dominant parent, 1 from the secondary.
/// </summary>
public ParentLineage DominantParent { get; init; }
/// <summary>
/// True when the PC is actively trying to pass as their dominant
/// parent's clade. Toggles on the character sheet; consulted by
/// Phase 6.5 M5 passing-detection rolls.
/// </summary>
public bool PassingActive { get; set; }
/// <summary>
/// Phase 6.5 M5 — currently-active scent mask tier (if any). The mask
/// suppresses scent-based detection per its tier. Phase 6.5 M5 ships
/// the static tier flag; Phase 8's clock model adds time-based
/// expiry alongside daily wear.
/// </summary>
public ScentMaskTier ActiveMaskTier { get; set; } = ScentMaskTier.None;
/// <summary>
/// NPC ids who have personally detected this PC is hybrid. Permanent
/// once added — disabling Passing later doesn't undo the discovery
/// for that specific NPC. Phase 6.5 M5 populates this; M4 reserves it
/// in the schema so save round-trip works pre- and post-M5.
/// </summary>
public HashSet<int> NpcsWhoKnow { get; } = new();
/// <summary>
/// Convenience: which clade the PC presents as for casual scent reads
/// (used by Phase 6.5 M5 passing logic and Phase 7 dialogue gates).
/// Returns the dominant parent's clade id.
/// </summary>
public string PresentingCladeId =>
DominantParent == ParentLineage.Sire ? SireClade : DamClade;
/// <summary>
/// Convenience: which species the PC presents as. Same logic as
/// <see cref="PresentingCladeId"/>.
/// </summary>
public string PresentingSpeciesId =>
DominantParent == ParentLineage.Sire ? SireSpecies : DamSpecies;
}
/// <summary>
/// Phase 6.5 M4 — which parent in a hybrid PC's lineage is the
/// dominant/secondary expression. Sire = paternal lineage, Dam = maternal
/// lineage; the choice is OOC (no gender semantics for the character
/// themselves, just for the parents' lineages).
/// </summary>
public enum ParentLineage : byte
{
Sire = 0,
Dam = 1,
}
/// <summary>
/// Phase 6.5 M5 — scent-mask tier suppressing hybrid detection. Maps to
/// the consumable items in <c>items.json</c>:
/// None — no mask active
/// Basic — scent_mask_basic; advantage on PC Deception roll
/// Military — scent_mask_military; auto-suppresses scent detection
/// DeepCover — scent_mask_deep_cover; auto-suppresses, even Superior Scent
///
/// Per the Phase 6.5 plan §4.7. Phase 8's clock + rest model adds
/// time-based expiry; M5 carries the tier as static state.
/// </summary>
public enum ScentMaskTier : byte
{
None = 0,
Basic = 1,
Military = 2,
DeepCover = 3,
}