Fix the southward migration: explore direction had a hardcoded +Y bias
Root cause of towns (East worst) drifting south: when an exploring soul was at its anchor with no outward vector, the fallback direction was Vector2(PersonalityModifier, 1 - Abs(PersonalityModifier)) - and with PersonalityModifier tiny (+-0.2) that Y-component was always ~+0.9. +Y is south, so every town's explorers got shoved into the southern corner. East showed it worst (selfish souls strip resources fastest, so they explore most). Now the at-anchor direction comes from the soul's identity hash, spread around the full compass. Supporting fixes: exploration is bounded to a ~140-unit home range (forage out and back, don't march to the horizon); private homesteads clamp to within 120 units of the town anchor (selfish souls sprawl on their OWN land, not into a neighbor's). Result: every East home now founds near the east anchor (x~870 vs the anchor's 962), none in South's territory, and East is the healthiest town. Also widened the map view (880 -> 1060px) to use more of the window - the world grew to 1152 but the render was a fixed square, so it looked no bigger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+40
-8
@@ -98,13 +98,37 @@ public class Npc
|
|||||||
{
|
{
|
||||||
Activity = "exploring for new ground";
|
Activity = "exploring for new ground";
|
||||||
Vector2 anchor = gm.TownAnchor(HomeTown);
|
Vector2 anchor = gm.TownAnchor(HomeTown);
|
||||||
Vector2 outward = (Position - anchor);
|
Vector2 fromHome = Position - anchor;
|
||||||
if (outward.LengthSquared() < 1f)
|
float distHome = fromHome.Length();
|
||||||
outward = new Vector2(PersonalityModifier, 1f - Mathf.Abs(PersonalityModifier));
|
|
||||||
outward = outward.Normalized();
|
// Stay in the home range. A person forages OUT from their village and
|
||||||
// A little personality-driven veer so explorers fan out, not conga-line.
|
// back, they don't march to the horizon — so past a search radius they
|
||||||
outward = outward.Rotated(PersonalityModifier * 1.2f);
|
// turn homeward. This keeps a strip-happy town (the East) from slowly
|
||||||
Position += outward * MoveSpeed;
|
// 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);
|
SenseNodes(gm.World);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -721,7 +745,14 @@ public class Npc
|
|||||||
Enter(NpcState.Gathering); // no stone yet — go get it
|
Enter(NpcState.Gathering); // no stone yet — go get it
|
||||||
return;
|
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 spot = _hasHomeSpot ? _homeSpot : Position;
|
||||||
|
Vector2 off = spot - anchor;
|
||||||
|
if (off.Length() > 120f) spot = anchor + off.Normalized() * 120f;
|
||||||
mine = Building.NewShelter(spot);
|
mine = Building.NewShelter(spot);
|
||||||
mine.Town = HomeTown;
|
mine.Town = HomeTown;
|
||||||
gm.Buildings.Add(mine);
|
gm.Buildings.Add(mine);
|
||||||
@@ -729,7 +760,8 @@ public class Npc
|
|||||||
// Don't clear the homeless drive yet — a foundation isn't a home.
|
// 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
|
// It resets only once the roof is on (below), so the builder keeps
|
||||||
// supplying walls and roof instead of stalling at the foundation.
|
// 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";
|
Activity = "building my own home";
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ public partial class Visualization : Node2D
|
|||||||
|
|
||||||
// Layout: 1600×900 window — map fills the left square, panel the right.
|
// Layout: 1600×900 window — map fills the left square, panel the right.
|
||||||
private const float MapOffset = 10f;
|
private const float MapOffset = 10f;
|
||||||
private const float MapPixels = 880f;
|
private const float MapPixels = 1060f; // fills most of the 1600px window
|
||||||
private const float CellPixels = MapPixels / World.GridSize; // ~3.44
|
private const float CellPixels = MapPixels / World.GridSize;
|
||||||
private const float PanelX = 910f;
|
private const float PanelX = 1085f;
|
||||||
private const float PanelWidth = 680f;
|
private const float PanelWidth = 505f;
|
||||||
|
|
||||||
private static readonly Color PanelBg = new(0.10f, 0.10f, 0.12f);
|
private static readonly Color PanelBg = new(0.10f, 0.10f, 0.12f);
|
||||||
private static readonly Color MapBg = new(0.13f, 0.13f, 0.15f);
|
private static readonly Color MapBg = new(0.13f, 0.13f, 0.15f);
|
||||||
|
|||||||
Reference in New Issue
Block a user