diff --git a/backend/app/engine.py b/backend/app/engine.py index de05917..8a3e0b9 100644 --- a/backend/app/engine.py +++ b/backend/app/engine.py @@ -399,8 +399,10 @@ class GameEngine: alive_agents = db.query(Agent).filter(Agent.status == "Alive").all() for agent in alive_agents: - # --- Sickness Mechanics (Phase 15) --- # 1. Contracting Sickness + # Phase 20-B: Check if sheltered + agent.is_sheltered = agent.location in ["tree_left", "tree_right"] + if not agent.is_sick: sickness_chance = 0.01 # Base 1% per tick (every 5s) @@ -411,6 +413,10 @@ class GameEngine: elif current_weather == "Stormy": sickness_chance += 0.10 + # Phase 20-B: Shelter mitigation (Reduce sickness chance by 80%) + if agent.is_sheltered and current_weather in ["Rainy", "Stormy"]: + sickness_chance *= 0.2 + # Immunity impact (Higher immunity = lower chance) # Immunity 50 -> -2.5%, Immunity 100 -> -5% sickness_chance -= (agent.immunity / 2000.0) @@ -442,7 +448,13 @@ class GameEngine: base_decay = BASE_ENERGY_DECAY_PER_TICK decay = base_decay * config.energy_decay_multiplier decay *= phase_mod.get("energy_decay", 1.0) - decay *= weather_mod.get("energy_modifier", 1.0) + + weather_decay_mod = weather_mod.get("energy_modifier", 1.0) + # Phase 20-B: Shelter mitigation (Reduce weather energy penalty by 80%) + if agent.is_sheltered and weather_decay_mod > 1.0: + weather_decay_mod = 1.0 + (weather_decay_mod - 1.0) * 0.2 + + decay *= weather_decay_mod agent.energy = max(0, agent.energy - int(decay)) @@ -669,6 +681,13 @@ class GameEngine: new_location = random.choice(["tree_left", "tree_right"]) should_update = True + # Phase 20-B: Seek Shelter during Storms + elif world.weather == "Stormy" and not agent.is_sheltered: + if agent.current_action != "Seek Shelter": + new_action = "Seek Shelter" + new_location = random.choice(["tree_left", "tree_right"]) + should_update = True + # 1.5. Sickness Handling (Phase 16) elif agent.is_sick: inv = self._get_inventory(agent) @@ -877,12 +896,13 @@ class GameEngine: """Fire-and-forget LLM call to generate agent speech.""" try: class AgentSnapshot: - def __init__(self, name, personality, hp, energy, mood): + def __init__(self, name, personality, hp, energy, mood, is_sheltered=False): self.name = name self.personality = personality self.hp = hp self.energy = energy self.mood = mood + self.is_sheltered = is_sheltered agent_snapshot = AgentSnapshot( agent_name, agent_personality, agent_hp, agent_energy, agent_mood @@ -914,21 +934,23 @@ class GameEngine: agent_data = { "id": agent.id, "name": agent.name, "personality": agent.personality, "hp": agent.hp, "energy": agent.energy, "mood": agent.mood, - "mood_state": agent.mood_state + "mood_state": agent.mood_state, "is_sheltered": agent.is_sheltered } try: class AgentSnapshot: - def __init__(self, name, personality, hp, energy, mood): + def __init__(self, name, personality, hp, energy, mood, is_sheltered=False): self.name = name self.personality = personality self.hp = hp self.energy = energy self.mood = mood + self.is_sheltered = is_sheltered agent_snapshot = AgentSnapshot( agent_data["name"], agent_data["personality"], - agent_data["hp"], agent_data["energy"], agent_data["mood"] + agent_data["hp"], agent_data["energy"], agent_data["mood"], + agent_data.get("is_sheltered", False) ) text = await llm_service.generate_idle_chat(agent_snapshot, weather, time_of_day) diff --git a/backend/app/llm.py b/backend/app/llm.py index 8ee27d8..8e884af 100644 --- a/backend/app/llm.py +++ b/backend/app/llm.py @@ -211,6 +211,7 @@ class LLMService: f"You are {agent.name}. " f"Personality: {agent.personality}. " f"Current Status: HP={agent.hp}, Energy={agent.energy}. " + f"Shelter Status: {'Under shelter (safe from weather)' if agent.is_sheltered else 'Exposed (vulnerable to weather)'}. " f"You live on a survival island. " f"Relevant Memories:\n{memory_context}\n" f"React to the following event briefly (under 20 words). " @@ -276,6 +277,7 @@ class LLMService: f"You are {agent.name}. " f"Personality: {agent.personality}. " f"Current Status: HP={agent.hp}, Energy={agent.energy}. " + f"Shelter Status: {'Under shelter (protected)' if agent.is_sheltered else 'Exposed to elements'}. " f"You are stranded on a survival island. " f"It is currently {time_of_day} and the weather is {weather}. " f"Say something brief (under 15 words) about your situation or thoughts. " diff --git a/backend/app/models.py b/backend/app/models.py index 935b7a5..756fc02 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -63,6 +63,9 @@ class Agent(Base): # Relationship 2.0 (Phase 17-B) social_role = Column(String(20), default="neutral") # leader, follower, loner, neutral + # Shelter System (Phase 20-B) + is_sheltered = Column(Boolean, default=False) + def __repr__(self): return f"" @@ -88,7 +91,8 @@ class Agent(Base): "location": self.location, "is_sick": self.is_sick, "immunity": self.immunity, - "social_role": self.social_role + "social_role": self.social_role, + "is_sheltered": self.is_sheltered } diff --git a/unity-client/Assets/Scripts/AgentVisual.cs b/unity-client/Assets/Scripts/AgentVisual.cs index aa2900b..2c76961 100644 --- a/unity-client/Assets/Scripts/AgentVisual.cs +++ b/unity-client/Assets/Scripts/AgentVisual.cs @@ -211,6 +211,22 @@ namespace TheIsland.Visual // Phase 19: Smooth UI Bar Transitions UpdateSmoothBars(); + + // Phase 20-C: Hard boundary enforcement (No water allowed!) + ClampPosition(); + } + + private void ClampPosition() + { + // Safety boundaries for the island beach - Phase 20-C.2 Recalibrated (Shore at Z=7.0) + // X: ~[-25, 25], Z: ~[-10, 6.5] + float clampedX = Mathf.Clamp(transform.position.x, -25f, 25f); + float clampedZ = Mathf.Clamp(transform.position.z, -10f, 6.5f); // Stay clearly on land + + if (clampedX != transform.position.x || clampedZ != transform.position.z) + { + transform.position = new Vector3(clampedX, transform.position.y, clampedZ); + } } private void UpdateGrounding() @@ -223,6 +239,13 @@ namespace TheIsland.Visual float bopY = (_spriteRenderer != null) ? _spriteRenderer.transform.localPosition.y : 0; float shadowScale = Mathf.Clamp(1.0f - (bopY * 0.5f), 0.5f, 1.2f); _shadowObj.transform.localScale = new Vector3(1.2f * shadowScale, 0.6f * shadowScale, 1f); + + // Phase 20-B: Darker shadow when sheltered (under tree) + if (_shadowRenderer != null) + { + float targetAlpha = (_currentData != null && _currentData.is_sheltered) ? 0.6f : 0.3f; + _shadowRenderer.color = new Color(0, 0, 0, Mathf.Lerp(_shadowRenderer.color.a, targetAlpha, Time.deltaTime * 5f)); + } } if (_isMoving) @@ -1340,7 +1363,7 @@ namespace TheIsland.Visual } /// - /// Display social role indicator based on agent's role. + /// Display social role and shelter indicators based on agent's state. /// private void UpdateSocialRoleDisplay() { @@ -1354,9 +1377,11 @@ namespace TheIsland.Visual _ => "" }; - // Append role icon to name (strip any existing icons first) - string baseName = _currentData.name; - _nameLabel.text = baseName + roleIcon; + // Phase 20-B: Shelter Icon + string shelterIcon = (_currentData != null && _currentData.is_sheltered) ? " 🏠" : ""; + + // Update name label with icons + _nameLabel.text = _currentData.name + roleIcon + shelterIcon; } #endregion diff --git a/unity-client/Assets/Scripts/GameManager.cs b/unity-client/Assets/Scripts/GameManager.cs index 58a4b0a..1a437bb 100644 --- a/unity-client/Assets/Scripts/GameManager.cs +++ b/unity-client/Assets/Scripts/GameManager.cs @@ -574,18 +574,19 @@ namespace TheIsland.Core switch (location.ToLower()) { case "tree_left": - return new Vector3(-10f, 0f, 8f); + return new Vector3(-12f, 0f, 5.0f); // Phase 20-C.2: Recalibrated case "tree_right": - return new Vector3(10f, 0f, 8f); + return new Vector3(13f, 0f, 5.5f); // Phase 20-C.2: Recalibrated case "campfire": case "center": return new Vector3(0f, 0f, 0f); case "water": case "beach": - return new Vector3(Random.Range(-5, 5), 0f, 4f); + // Shore starts at Z=7.0. Safe beach area is Z=[3, 6] + return new Vector3(Random.Range(-5, 5), 0f, Random.Range(4, 6)); case "nearby": - // Move to random nearby spot (wandering) - return new Vector3(Random.Range(-12, 12), 0f, Random.Range(-2, 6)); + // Wandering constrained to dry land Z=[-6, 6] + return new Vector3(Random.Range(-15, 15), 0f, Random.Range(-6, 6)); case "herb_patch": // Phase 16: Herb gathering location return new Vector3(-8f, 0f, -5f); diff --git a/unity-client/Assets/Scripts/Models.cs b/unity-client/Assets/Scripts/Models.cs index be7697b..d547827 100644 --- a/unity-client/Assets/Scripts/Models.cs +++ b/unity-client/Assets/Scripts/Models.cs @@ -59,6 +59,9 @@ namespace TheIsland.Models // Relationship 2.0 (Phase 17-B) public string social_role; // "leader", "follower", "loner", "neutral" + // Shelter System (Phase 20-B) + public bool is_sheltered; + public bool IsAlive => status == "Alive"; } diff --git a/unity-client/Assets/Scripts/Visual/EnvironmentManager.cs b/unity-client/Assets/Scripts/Visual/EnvironmentManager.cs index 5948d0c..5ee72aa 100644 --- a/unity-client/Assets/Scripts/Visual/EnvironmentManager.cs +++ b/unity-client/Assets/Scripts/Visual/EnvironmentManager.cs @@ -276,9 +276,9 @@ namespace TheIsland.Visual _groundPlane = GameObject.CreatePrimitive(PrimitiveType.Quad); _groundPlane.name = "GroundPlane"; _groundPlane.transform.SetParent(transform); - _groundPlane.transform.position = new Vector3(0, -0.5f, 5); + _groundPlane.transform.position = new Vector3(0, -0.5f, 0); // Phase 20-C.2: Center ground _groundPlane.transform.rotation = Quaternion.Euler(90, 0, 0); - _groundPlane.transform.localScale = new Vector3(40, 20, 1); + _groundPlane.transform.localScale = new Vector3(80, 24, 1); // Phase 20-C.2: Larger sand area Z=[-12, 12] // Create sand texture _groundMaterial = new Material(Shader.Find("Unlit/Texture")); @@ -325,9 +325,9 @@ namespace TheIsland.Visual _waterPlane = GameObject.CreatePrimitive(PrimitiveType.Quad); _waterPlane.name = "WaterPlane"; _waterPlane.transform.SetParent(transform); - _waterPlane.transform.position = new Vector3(0, -0.3f, 12); + _waterPlane.transform.position = new Vector3(0, -0.3f, 15); // Phase 20-C.2: Move water back (Shore starts at ~7.0) _waterPlane.transform.rotation = Quaternion.Euler(90, 0, 0); - _waterPlane.transform.localScale = new Vector3(60, 15, 1); + _waterPlane.transform.localScale = new Vector3(100, 16, 1); // Range Z=[7, 23] // Create water material if (customWaterMaterial != null) @@ -409,16 +409,17 @@ namespace TheIsland.Visual private void CreateDecorations() { - // Create palm tree silhouettes - CreatePalmTree(new Vector3(-8, 0, 8), 2.5f); - CreatePalmTree(new Vector3(-10, 0, 10), 3f); - CreatePalmTree(new Vector3(9, 0, 7), 2.2f); - CreatePalmTree(new Vector3(11, 0, 9), 2.8f); + // Create palm tree silhouettes - Recalibrated for Phase 20-C (Stay on Land Z < 4.5) + // Create palm tree silhouettes - Recalibrated for Phase 20-C.2 (Safe dry land Z < 6.0) + CreatePalmTree(new Vector3(-12, 0, 5.0f), 2.8f); + CreatePalmTree(new Vector3(-15, 0, 4.0f), 3.2f); + CreatePalmTree(new Vector3(13, 0, 5.5f), 2.5f); + CreatePalmTree(new Vector3(16, 0, 4.5f), 3.0f); - // Create rocks - CreateRock(new Vector3(-5, 0, 4), 0.5f); - CreateRock(new Vector3(6, 0, 5), 0.7f); - CreateRock(new Vector3(-7, 0, 6), 0.4f); + // Create rocks - Recalibrated for Phase 20-C.2 + CreateRock(new Vector3(-8, 0, 4.5f), 0.5f); + CreateRock(new Vector3(10, 0, 5.5f), 0.7f); + CreateRock(new Vector3(-14, 0, 3.0f), 0.4f); CreateGroundDetails(); }