diff --git a/scripts/NPC.cs b/scripts/NPC.cs index 0bb5b1c..887a6e3 100644 --- a/scripts/NPC.cs +++ b/scripts/NPC.cs @@ -98,13 +98,37 @@ public class Npc { Activity = "exploring for new ground"; Vector2 anchor = gm.TownAnchor(HomeTown); - Vector2 outward = (Position - anchor); - if (outward.LengthSquared() < 1f) - outward = new Vector2(PersonalityModifier, 1f - Mathf.Abs(PersonalityModifier)); - outward = outward.Normalized(); - // A little personality-driven veer so explorers fan out, not conga-line. - outward = outward.Rotated(PersonalityModifier * 1.2f); - Position += outward * MoveSpeed; + Vector2 fromHome = Position - anchor; + float distHome = fromHome.Length(); + + // Stay in the home range. A person forages OUT from their village and + // back, they don't march to the horizon — so past a search radius they + // turn homeward. This keeps a strip-happy town (the East) from slowly + // migrating into its neighbors instead of settling its own ground. + const float SearchRadius = 140f; + Vector2 dir; + if (distHome > SearchRadius) + { + dir = (-fromHome).Normalized(); // head back toward home + dir = dir.Rotated(PersonalityModifier * 0.6f); // slight veer, still homeward + } + else + { + // Outward from home in a per-soul direction. When right at the + // anchor there's no outward vector, so pick an angle from the + // soul's identity — NOT a fixed vector (the old default leaned + // hard +Y, i.e. south, quietly funnelling every town's explorers + // into the southern corner). + if (distHome > 1f) + dir = fromHome.Normalized(); + else + { + float ang = (Name.GetHashCode() & 0xFFFF) / 65535f * Mathf.Tau; + dir = new Vector2(Mathf.Cos(ang), Mathf.Sin(ang)); + } + dir = dir.Rotated(PersonalityModifier * 1.4f + 0.6f); // circle the home range + } + Position += dir * MoveSpeed; SenseNodes(gm.World); } @@ -721,7 +745,14 @@ public class Npc Enter(NpcState.Gathering); // no stone yet — go get it return; } + // Homestead on your OWN town's land — selfish souls sprawl (a + // wide range, no clustering to the shared center) but they build + // in the east if they're an East soul, not wherever they drifted + // into a neighbor's territory. + Vector2 anchor = gm.TownAnchor(HomeTown); Vector2 spot = _hasHomeSpot ? _homeSpot : Position; + Vector2 off = spot - anchor; + if (off.Length() > 120f) spot = anchor + off.Normalized() * 120f; mine = Building.NewShelter(spot); mine.Town = HomeTown; gm.Buildings.Add(mine); @@ -729,7 +760,8 @@ public class Npc // Don't clear the homeless drive yet — a foundation isn't a home. // It resets only once the roof is on (below), so the builder keeps // supplying walls and roof instead of stalling at the foundation. - Godot.GD.Print($"[Homestead] day {gm.Day}: {Name} ({HomeTown}) breaks ground on a private home."); + Godot.GD.Print($"[Homestead] day {gm.Day}: {Name} ({HomeTown}) breaks ground at " + + $"({mine.Site.X:0},{mine.Site.Y:0}) — {(mine.Site - gm.TownAnchor(HomeTown)).Length():0} from home."); } Activity = "building my own home"; diff --git a/scripts/Visualization.cs b/scripts/Visualization.cs index c11f805..83ff439 100644 --- a/scripts/Visualization.cs +++ b/scripts/Visualization.cs @@ -14,10 +14,10 @@ public partial class Visualization : Node2D // Layout: 1600×900 window — map fills the left square, panel the right. private const float MapOffset = 10f; - private const float MapPixels = 880f; - private const float CellPixels = MapPixels / World.GridSize; // ~3.44 - private const float PanelX = 910f; - private const float PanelWidth = 680f; + private const float MapPixels = 1060f; // fills most of the 1600px window + private const float CellPixels = MapPixels / World.GridSize; + private const float PanelX = 1085f; + private const float PanelWidth = 505f; private static readonly Color PanelBg = new(0.10f, 0.10f, 0.12f); private static readonly Color MapBg = new(0.13f, 0.13f, 0.15f);