namespace WorldSim; /// /// Trade evaluation and execution (SPEC §9). South MVP: generous, voluntary, /// no currency — a gift economy where the ratio favors the receiver. /// /// Soul consequences: a generous completed exchange eases both parties' /// imprints (external perception — the world pressed kindly on them). /// Later soul types change the ratio logic only; the mechanism is shared. /// public static class TradeSystem { /// Trades since the last daily stats snapshot. public static int TradesToday; /// Refusals since the last daily stats snapshot. public static int RefusalsToday; /// /// A hungry villager asks another for food, face to face. /// /// Willingness comes from the soul (or from a warm history with this /// particular person). Refusing the hungry while holding food is a /// deliberate darkening of another — it marks the refuser's soul /// (others-regard debris, per DESIGN.md) and leaves a cold echo in the /// asker's memory. The world keeps score the only way it knows how. /// public static void RequestGift(Npc giver, Npc asker) { giver.Inventory.TryGetValue(MaterialKind.Food, out float giverFood); bool hasFood = giverFood > 3f; bool willing = giver.Soul.Generosity >= 0.3f || giver.GetWarmth(asker.Name) > 0.5f; if (hasFood && willing) { Execute(giver, asker, MaterialKind.Food); return; } if (hasFood && !willing) { // Refused with a full pack — the act that costs. RefusalsToday++; asker.RecordEcho(giver.Name, -0.25f); giver.Imprint.OthersRegard = Godot.Mathf.Clamp(giver.Imprint.OthersRegard + 0.02f, 0f, 1f); StatsLogger.LogRefusal(giver, asker); } // Empty-handed is not refusal: no debris, no echo. You cannot darken // your soul with what you do not have. } /// /// Need-based: the trigger is a person in need, not an inventory gap. /// Food flows to the hungry first; raw material surplus vs. genuine /// lack remains as a fallback. /// public static bool CanTrade(Npc giver, Npc receiver, out MaterialKind material) { material = MaterialKind.Food; // The ungenerous do not give. Not from an empty pack — from a full one. if (giver.Soul.Generosity < 0.3f) return false; giver.Inventory.TryGetValue(MaterialKind.Food, out float giverFood); receiver.Inventory.TryGetValue(MaterialKind.Food, out float receiverFood); if (receiver.Nourishment < 0.6f && receiverFood < 2f && giverFood > 3f) return true; foreach (var kv in giver.Inventory) { bool receiverLacks = !receiver.Inventory.TryGetValue(kv.Key, out float has) || has < 2f; if (kv.Value > 5f && receiverLacks) { material = kv.Key; return true; } } return false; } public static void Execute(Npc giver, Npc receiver, MaterialKind material) { TradesToday++; // Generosity decides how much of the surplus is given. float surplus = giver.Inventory[material] - 2f; float given = surplus * giver.Soul.Generosity; giver.Inventory[material] -= given; receiver.Inventory.TryGetValue(material, out float has); receiver.Inventory[material] = has + given; StatsLogger.LogGift(giver, receiver, material, given); // Warmth lands on both sides — more on the one who was fed. receiver.RecordEcho(giver.Name, +0.15f); giver.RecordEcho(receiver.Name, +0.05f); // The kindness lands on both atmospheres — gently. giver.Imprint.ExternalPerception -= 0.002f; receiver.Imprint.ExternalPerception -= 0.002f; giver.Imprint.Clamp(); receiver.Imprint.Clamp(); // TODO(Week 3): soul-imprint-weighted PARTNER selection (trusted vs // tainted history) — the constrained-emergence reference pattern (SPEC §7). } }