//! Layer 3: Corporate behavioral agents (D-178). //! //! Six behavioral archetypes from D-175 / Burnelli-Sheldon: //! //! Producer — maximises output, low trade aggression //! Distributor — volume-focused, aggressive trade, thin margin //! Specialist — premium pricing, narrow focus, low trade //! Monopolist — withholds supply to maintain scarcity premium //! Cooperative — fair pricing, community stability orientation //! Intermediary — arbitrage-focused, high trade, lower own production //! //! Archetypes are loaded from `corporations.behavioral_archetype` in the DB. //! If NULL, the archetype is inferred from the `specialization` field text. //! //! Parameters apply to per-corp production in each simulation tick. //! Trade-layer archetype effects (corp-level bid/ask) are deferred to a //! future sprint when the event port (D-180) and IPC bridge are in place. //! //! The D-180 event port stubs previously in this file have been replaced by //! the full implementation in `events.rs`. use std::collections::BTreeMap; // --------------------------------------------------------------------------- // Archetype enum // --------------------------------------------------------------------------- #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Archetype { Producer, Distributor, Specialist, Monopolist, Cooperative, Intermediary, } impl Archetype { /// Parse from DB string (case-insensitive). pub fn from_str(s: &str) -> Option { match s.to_lowercase().trim() { "producer" => Some(Archetype::Producer), "distributor" => Some(Archetype::Distributor), "specialist" => Some(Archetype::Specialist), "monopolist" => Some(Archetype::Monopolist), "cooperative" => Some(Archetype::Cooperative), "intermediary" => Some(Archetype::Intermediary), _ => None, } } /// Infer archetype from `specialization` field free text. /// /// Heuristic: look for domain keywords that map to behavioral patterns. /// Falls back to `Producer` (the most neutral, maximises output). pub fn infer_from_specialization(spec: &str) -> Self { let s = spec.to_lowercase(); if s.contains("freight") || s.contains("logistics") || s.contains("hauler") || s.contains("cargo") { Archetype::Distributor } else if s.contains("arbitr") || s.contains("trading company") || s.contains("brokerage") || s.contains("intermediar") { Archetype::Intermediary } else if s.contains("cooperative") || s.contains("mutu") || s.contains("negociant") { Archetype::Cooperative } else if s.contains("whisky") || s.contains("wine") || s.contains("lager") || s.contains("precision") || s.contains("bespoke") || s.contains("longevity") { Archetype::Specialist } else if s.contains("infrastructure") && (s.contains("gate") || s.contains("span")) { // Gate Corp maintains infrastructure monopoly Archetype::Monopolist } else { Archetype::Producer } } /// Behavioral parameters for this archetype. pub fn params(self) -> ArchetypeParams { match self { // Producer: higher output, normal trade participation Archetype::Producer => ArchetypeParams { production_scale: 1.15, supply_withheld: 0.0, price_premium: 0.0, }, // Distributor: leaner production, price discount to move volume Archetype::Distributor => ArchetypeParams { production_scale: 0.90, supply_withheld: 0.0, price_premium: -0.03, }, // Specialist: normal production, commands a premium Archetype::Specialist => ArchetypeParams { production_scale: 1.0, supply_withheld: 0.0, price_premium: 0.10, }, // Monopolist: constrained output, withholds supply, premium Archetype::Monopolist => ArchetypeParams { production_scale: 0.80, supply_withheld: 0.25, price_premium: 0.20, }, // Cooperative: normal production, slight discount for community access Archetype::Cooperative => ArchetypeParams { production_scale: 1.0, supply_withheld: 0.0, price_premium: -0.05, }, // Intermediary: lower own production, relies on traded goods Archetype::Intermediary => ArchetypeParams { production_scale: 0.70, supply_withheld: 0.0, price_premium: -0.01, }, } } } // --------------------------------------------------------------------------- // Parameter struct // --------------------------------------------------------------------------- /// Per-tick behavioral parameters for a corporation. #[derive(Debug, Clone)] pub struct ArchetypeParams { /// Multiplier on BASELINE_CAPACITY for this corp's production. pub production_scale: f64, /// Fraction of this tick's output that is withheld from the node's /// stockpile (Monopolist strategy). Range [0.0, 1.0]. pub supply_withheld: f64, /// Additive price premium on goods this corp produces. /// Applied to the node price signal for their primary commodity. /// Positive → price pressure up. Negative → price pressure down. pub price_premium: f64, } // --------------------------------------------------------------------------- // Corpus load // --------------------------------------------------------------------------- /// Build archetype map from the raw DB data supplied by the caller. /// /// `corp_data`: Vec of (corp_id, behavioral_archetype_opt, specialization_opt) pub fn build_archetype_map( corp_data: Vec<(String, Option, Option)>, ) -> BTreeMap { corp_data .into_iter() .map(|(corp_id, archetype_str, specialization)| { let archetype = archetype_str .as_deref() .and_then(Archetype::from_str) .unwrap_or_else(|| { specialization .as_deref() .map(Archetype::infer_from_specialization) .unwrap_or(Archetype::Producer) }); (corp_id, archetype) }) .collect() }