using System; namespace AfterHours.Customers { /// /// Kern-Mechanik des Spiels: Wie wohl fühlt sich ein Kunde gerade? /// Fällt der Komfort unter , legt der Kunde /// die Ware zurück und geht. /// /// Engine-frei und damit testbar – siehe Tests/EditMode/ComfortModelTests.cs. /// public static class ComfortModel { public const float AbandonThreshold = 0.25f; /// Komfortverlust pro anderem Kunden im Nahbereich. private const float CrowdingPenaltyPerPerson = 0.09f; /// Malus, wenn Personal ungefragt in Reichweite steht. private const float UnwantedStaffPenalty = 0.18f; /// Malus, wenn das Regal von der Straße aus einsehbar ist. private const float ExposedShelfPenalty = 0.22f; /// Erholung pro Sekunde, wenn niemand stört. private const float RecoveryPerSecond = 0.12f; /// /// Zielkomfort für die aktuelle Situation (0..1). /// /// Charaktereigenschaft des Kunden, 0..1. Höher = unerschrockener. /// Diskretionsbedarf des betrachteten Produkts, 0..1. /// Andere Kunden im Nahbereich. /// Personal steht daneben, ohne dass Beratung gewünscht war. /// Regal ist vom Schaufenster aus einsehbar. public static float EvaluateTarget( float baseTolerance, float discretionDemand, int nearbyCustomers, bool staffNearbyUnrequested, bool shelfExposedToStreet) { if (nearbyCustomers < 0) throw new ArgumentOutOfRangeException(nameof(nearbyCustomers)); float tolerance = Clamp01(baseTolerance); float sensitivity = Clamp01(discretionDemand); // Ein toleranter Kunde bei unkritischer Ware ist praktisch unstörbar. float exposure = sensitivity * (1f - tolerance * 0.6f); float penalty = 0f; penalty += nearbyCustomers * CrowdingPenaltyPerPerson; if (staffNearbyUnrequested) penalty += UnwantedStaffPenalty; if (shelfExposedToStreet) penalty += ExposedShelfPenalty; return Clamp01(1f - penalty * exposure * 2f); } /// Bewegt den aktuellen Komfort auf den Zielwert zu (Frame-Schritt). public static float Step(float current, float target, float deltaTime) { if (target >= current) { // Erholung ist träge … return Clamp01(current + RecoveryPerSecond * deltaTime); } // … Unbehagen schlägt sofort durch. return Clamp01(target); } public static bool ShouldAbandon(float comfort) => comfort < AbandonThreshold; private static float Clamp01(float v) => v < 0f ? 0f : (v > 1f ? 1f : v); } }