using Theriapolis.Core.Data; namespace Theriapolis.Core.Rules.Character; /// /// Phase 6.5 M4 — runtime state for a hybrid character. /// /// Hybrids are blended from two parent lineages: a Sire (paternal /// lineage) and a Dam (maternal lineage), of two different clades. /// One parent is — 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 theriapolis-rpg-clades.md 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 ). /// /// 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. /// public sealed class HybridState { /// Sire (paternal-lineage) clade id, e.g. "canidae". public string SireClade { get; init; } = ""; /// Sire (paternal-lineage) species id, e.g. "wolf". public string SireSpecies { get; init; } = ""; /// Dam (maternal-lineage) clade id, e.g. "leporidae". public string DamClade { get; init; } = ""; /// Dam (maternal-lineage) species id, e.g. "rabbit". public string DamSpecies { get; init; } = ""; /// /// 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. /// public ParentLineage DominantParent { get; init; } /// /// 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. /// public bool PassingActive { get; set; } /// /// 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. /// public ScentMaskTier ActiveMaskTier { get; set; } = ScentMaskTier.None; /// /// 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. /// public HashSet NpcsWhoKnow { get; } = new(); /// /// 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. /// public string PresentingCladeId => DominantParent == ParentLineage.Sire ? SireClade : DamClade; /// /// Convenience: which species the PC presents as. Same logic as /// . /// public string PresentingSpeciesId => DominantParent == ParentLineage.Sire ? SireSpecies : DamSpecies; } /// /// 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). /// public enum ParentLineage : byte { Sire = 0, Dam = 1, } /// /// Phase 6.5 M5 — scent-mask tier suppressing hybrid detection. Maps to /// the consumable items in items.json: /// 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. /// public enum ScentMaskTier : byte { None = 0, Basic = 1, Military = 2, DeepCover = 3, }