From aee31e286f62cbd190c61d0eae28d0526f2f1dbe Mon Sep 17 00:00:00 2001 From: autocommit Date: Thu, 16 Apr 2026 22:02:36 -0700 Subject: [PATCH] =?UTF-8?q?feat(simulator):=20=E2=9C=A8=20Add=20logic=20to?= =?UTF-8?q?=20account=20for=20new=20collectible=20yield=20channels=20in=20?= =?UTF-8?q?the=20happiness=20pool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- src/simulator/crates/mc-happiness/src/pool.rs | 49 ++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/simulator/crates/mc-happiness/src/pool.rs b/src/simulator/crates/mc-happiness/src/pool.rs index 3284fdef..9a2df4a4 100644 --- a/src/simulator/crates/mc-happiness/src/pool.rs +++ b/src/simulator/crates/mc-happiness/src/pool.rs @@ -1,6 +1,7 @@ //! Happiness pool calculation, racial growth tiers, and Golden Age logic. use serde::{Deserialize, Serialize}; +use std::collections::BTreeSet; // ── Constants (match GDScript Happiness class) ───────────────────────── @@ -142,8 +143,9 @@ pub struct HappinessInput { pub ascension_active: bool, /// Total happiness from buildings (already summed by GDScript from DataLoader). pub building_happiness: i32, - /// Number of unique improved luxury resources. - pub unique_luxury_count: i32, + /// IDs of improved luxury resources owned by this player. Duplicates are + /// ignored — only unique luxury types grant happiness. + pub owned_luxuries: BTreeSet, /// Racial growth tier string (e.g., "balanced", "expansionist"). pub growth_tier: String, } @@ -175,6 +177,13 @@ pub struct GoldenAgeState { // ── Core functions ───────────────────────────────────────────────────── +/// Happiness contributed by unique luxury resources. +/// Each distinct luxury ID contributes `config.luxury_happiness`; duplicates +/// are ignored because `owned_luxuries` is a `BTreeSet`. +pub fn happiness_from_luxuries(owned_luxuries: &BTreeSet, config: &HappinessConfig) -> i32 { + owned_luxuries.len() as i32 * config.luxury_happiness +} + /// Calculate the full happiness breakdown for a player. pub fn calculate_happiness(input: &HappinessInput, config: &HappinessConfig) -> HappinessBreakdown { let tier = GrowthTier::parse_or_default(&input.growth_tier); @@ -193,7 +202,7 @@ pub fn calculate_happiness(input: &HappinessInput, config: &HappinessConfig) -> }; let base_unhappiness = city_unhappiness + citizen_unhappiness + war_weariness + ascension_penalty; - let luxury_happiness = input.unique_luxury_count * config.luxury_happiness; + let luxury_happiness = happiness_from_luxuries(&input.owned_luxuries, config); let total = (input.building_happiness + luxury_happiness) - base_unhappiness; let status = HappinessStatus::from_total(total); @@ -277,7 +286,7 @@ mod tests { units_in_enemy_territory: 0, ascension_active: false, building_happiness: 10, - unique_luxury_count: 2, + owned_luxuries: BTreeSet::from(["silk".into(), "gold_ore".into()]), growth_tier: "balanced".to_string(), } } @@ -458,7 +467,7 @@ mod tests { units_in_enemy_territory: 0, ascension_active: false, building_happiness: 0, - unique_luxury_count: 0, + owned_luxuries: BTreeSet::new(), growth_tier: String::new(), }; @@ -542,6 +551,34 @@ mod tests { assert!((get_combat_modifier(5) - 1.0).abs() < f64::EPSILON); } + // ── Luxury happiness tests ───────────────────────────────────────────── + + #[test] + fn luxury_happiness_empty_set_yields_zero() { + let config = HappinessConfig::default(); + assert_eq!(happiness_from_luxuries(&BTreeSet::new(), &config), 0); + } + + #[test] + fn luxury_happiness_three_unique_luxuries() { + let config = HappinessConfig::default(); + let luxuries = BTreeSet::from(["silk".into(), "amber".into(), "deepstone".into()]); + // 3 unique * LUXURY_HAPPINESS(4) = 12 + assert_eq!(happiness_from_luxuries(&luxuries, &config), 12); + } + + #[test] + fn luxury_happiness_duplicate_counted_once() { + let config = HappinessConfig::default(); + // BTreeSet deduplicates — inserting "silk" twice still yields one entry. + let mut luxuries = BTreeSet::new(); + luxuries.insert("silk".to_string()); + luxuries.insert("silk".to_string()); + luxuries.insert("amber".to_string()); + // 2 unique * 4 = 8, not 3 * 4 = 12 + assert_eq!(happiness_from_luxuries(&luxuries, &config), 8); + } + /// The breakdown carries both modifiers — make sure they are wired up /// from the raw helpers rather than being hardcoded/stale. #[test] @@ -557,7 +594,7 @@ mod tests { units_in_enemy_territory: 0, ascension_active: false, building_happiness: 0, - unique_luxury_count: 0, + owned_luxuries: BTreeSet::new(), growth_tier: "balanced".to_string(), }; let result = calculate_happiness(&input, &config);