Herding / flocking via GeneSociability (boids)
Social species (deer 0.8, wolf 0.6, chicken 0.55, boar 0.3) now school together: AnimalDecisionSystem scans nearby same-species neighbours (HerdScan) and, when wandering, a lone social animal steers toward the herd centroid (cohesion) while close members just mill about (separation). "Safety in numbers" shrinks the threat-detection radius in a group, so herded prey are calmer and scatter less. Asocial animals (sociability < 0.3) keep the old random wander. GeneSociability + trait; `animal` console prints insulation/sociability. Build + --check-content clean (42 genes, 41 traits). Not GUI-verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
5ac34f4dc7
commit
c0822de650
@@ -31,7 +31,8 @@
|
|||||||
"GeneGestationDays": 30,
|
"GeneGestationDays": 30,
|
||||||
"GeneLitterSize": 1,
|
"GeneLitterSize": 1,
|
||||||
"GeneHerbivory": 1.0,
|
"GeneHerbivory": 1.0,
|
||||||
"GeneToxinTolerance": 0.1
|
"GeneToxinTolerance": 0.1,
|
||||||
|
"GeneSociability": 0.8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -64,7 +65,8 @@
|
|||||||
"GeneBreedingSeason": 3,
|
"GeneBreedingSeason": 3,
|
||||||
"GeneGestationDays": 35,
|
"GeneGestationDays": 35,
|
||||||
"GeneLitterSize": 4,
|
"GeneLitterSize": 4,
|
||||||
"GeneCarnivory": 1.0
|
"GeneCarnivory": 1.0,
|
||||||
|
"GeneSociability": 0.6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -99,7 +101,8 @@
|
|||||||
"GeneHerbivory": 0.4,
|
"GeneHerbivory": 0.4,
|
||||||
"GeneCarnivory": 0.2,
|
"GeneCarnivory": 0.2,
|
||||||
"GeneOmnivory": 0.85,
|
"GeneOmnivory": 0.85,
|
||||||
"GeneToxinTolerance": 0.25
|
"GeneToxinTolerance": 0.25,
|
||||||
|
"GeneSociability": 0.3
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -129,7 +132,8 @@
|
|||||||
"GeneLitterSize": 6,
|
"GeneLitterSize": 6,
|
||||||
"GeneHerbivory": 0.8,
|
"GeneHerbivory": 0.8,
|
||||||
"GeneToxinTolerance": 0.15,
|
"GeneToxinTolerance": 0.15,
|
||||||
"GeneEggLaying": 1.0
|
"GeneEggLaying": 1.0,
|
||||||
|
"GeneSociability": 0.55
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -59,6 +59,12 @@
|
|||||||
{ "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex",
|
{ "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex",
|
||||||
"variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] },
|
"variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] },
|
||||||
|
|
||||||
|
// Социальность [0..1]: стадные/стайные виды держатся вместе (когезия при блуждании) и спокойнее в
|
||||||
|
// группе («безопасность в числе» снижает дальность реакции на хищника). См. AnimalDecisionSystem.
|
||||||
|
{ "defName": "GeneSociability", "parent": "BaseNumericGene", "label": "gene.sociability",
|
||||||
|
"default": 0.3, "min": 0.0, "max": 1.0, "spread": 0.05, "tags": ["animal", "behaviour"],
|
||||||
|
"effects": { "sociability": "value" } },
|
||||||
|
|
||||||
// Тип рождения (цикл зачатия): живородящие (eggLaying < 0.5) вынашивают и рожают живых детёнышей;
|
// Тип рождения (цикл зачатия): живородящие (eggLaying < 0.5) вынашивают и рожают живых детёнышей;
|
||||||
// яйцекладущие (≥ 0.5) после вынашивания ОТКЛАДЫВАЮТ яйца-сущности, которые инкубируются и
|
// яйцекладущие (≥ 0.5) после вынашивания ОТКЛАДЫВАЮТ яйца-сущности, которые инкубируются и
|
||||||
// вылупляются (см. AnimalPregnancySystem/EggSystem). Признак фиксирован по виду (spread/mutation 0).
|
// вылупляются (см. AnimalPregnancySystem/EggSystem). Признак фиксирован по виду (spread/mutation 0).
|
||||||
|
|||||||
@@ -1208,6 +1208,9 @@ public sealed class WorldScene : Scene
|
|||||||
$" reproduction: {(traits.Oviparous ? "oviparous (lays eggs)" : "viviparous (live birth)")}, "
|
$" reproduction: {(traits.Oviparous ? "oviparous (lays eggs)" : "viviparous (live birth)")}, "
|
||||||
+ $"gestation {traits.GestationDays:0} d, litter {traits.LitterSize:0.#}"
|
+ $"gestation {traits.GestationDays:0} d, litter {traits.LitterSize:0.#}"
|
||||||
);
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" behaviour: insulation {traits.Insulation:0.##}, sociability {traits.Sociability:0.##}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ public struct AnimalPhenotype
|
|||||||
/// <summary>Устойчивость к растительным ядам [0..1] — снижает дозу отравления токсичным кормом.</summary>
|
/// <summary>Устойчивость к растительным ядам [0..1] — снижает дозу отравления токсичным кормом.</summary>
|
||||||
public float ToxinTolerance;
|
public float ToxinTolerance;
|
||||||
|
|
||||||
|
/// <summary>Социальность [0..1] — тяга держаться стаи/стада (когезия + спокойствие в группе).</summary>
|
||||||
|
public float Sociability;
|
||||||
|
|
||||||
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
||||||
public float Lifespan;
|
public float Lifespan;
|
||||||
|
|
||||||
@@ -88,6 +91,7 @@ public struct AnimalPhenotype
|
|||||||
Carnivory = T("carnivory"),
|
Carnivory = T("carnivory"),
|
||||||
Omnivory = T("omnivory"),
|
Omnivory = T("omnivory"),
|
||||||
ToxinTolerance = T("toxinTolerance"),
|
ToxinTolerance = T("toxinTolerance"),
|
||||||
|
Sociability = T("sociability"),
|
||||||
Lifespan = T("lifespan"),
|
Lifespan = T("lifespan"),
|
||||||
BreedingSeason = (int)MathF.Round(Math.Clamp(T("breedingSeason"), 0f, 3f)),
|
BreedingSeason = (int)MathF.Round(Math.Clamp(T("breedingSeason"), 0f, 3f)),
|
||||||
GestationDays = T("gestationDays"),
|
GestationDays = T("gestationDays"),
|
||||||
|
|||||||
@@ -232,6 +232,10 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
private const float Interval = 0.6f;
|
private const float Interval = 0.6f;
|
||||||
private const float MateMoodFloor = 0.35f; // ниже — стресс/истощение, зверь не спаривается (A8)
|
private const float MateMoodFloor = 0.35f; // ниже — стресс/истощение, зверь не спаривается (A8)
|
||||||
|
private const float HerdMinSociability = 0.3f; // ниже — вид не стадный (когезия/безопасность выкл.)
|
||||||
|
private const float SafetyInNumbers = 0.5f; // макс. снижение радиуса реакции на угрозу в группе
|
||||||
|
private const int HerdFullCount = 6; // размер группы, при котором эффект «безопасность в числе» полон
|
||||||
|
private const float HerdSeparation = 1.5f; // ближе этого (в клетках) к центру — не сходимся плотнее
|
||||||
|
|
||||||
private readonly UtilityAi<AnimalContext> _brain;
|
private readonly UtilityAi<AnimalContext> _brain;
|
||||||
private readonly GameClock _clock;
|
private readonly GameClock _clock;
|
||||||
@@ -345,9 +349,28 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
// Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию.
|
// Настроение (A8): стресс/истощение (низкое настроение) подавляет тягу к спариванию.
|
||||||
var mood = self.GetComponent<Mood>();
|
var mood = self.GetComponent<Mood>();
|
||||||
|
|
||||||
|
// Стадность (boids): социальные виды считают центр/размер ближайшей группы соплеменников —
|
||||||
|
// для когезии при блуждании и «безопасности в числе» (спокойнее на угрозу в большой группе).
|
||||||
|
var sociability = o[i].Traits.Sociability;
|
||||||
|
var herdCount = 0;
|
||||||
|
var herdCenter = pos;
|
||||||
|
if (sociability >= HerdMinSociability)
|
||||||
|
{
|
||||||
|
HerdScan(pos, seeRadius, o[i].Species, self.Id, out herdCenter, out herdCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
var threatRadius =
|
||||||
|
herdCount > 0
|
||||||
|
? senseRadius
|
||||||
|
* (
|
||||||
|
1f
|
||||||
|
- SafetyInNumbers * MathF.Min(1f, herdCount / (float)HerdFullCount)
|
||||||
|
)
|
||||||
|
: senseRadius;
|
||||||
|
|
||||||
// Бегство (предатор-кластер): жертва, заметившая рядом хищника, спасается — это важнее
|
// Бегство (предатор-кластер): жертва, заметившая рядом хищника, спасается — это важнее
|
||||||
// любых нужд (инстинкт выживания). Сам хищник от добычи не бежит (см. IsThreatTo).
|
// любых нужд (инстинкт выживания). Сам хищник от добычи не бежит (см. IsThreatTo).
|
||||||
if (TryFindThreat(pos, senseRadius, o[i].Species, o[i].Traits, out var threatPos))
|
if (TryFindThreat(pos, threatRadius, o[i].Species, o[i].Traits, out var threatPos))
|
||||||
{
|
{
|
||||||
brain.Action = AnimalActions.Flee;
|
brain.Action = AnimalActions.Flee;
|
||||||
brain.TargetPlant = -1;
|
brain.TargetPlant = -1;
|
||||||
@@ -431,11 +454,31 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
default:
|
default:
|
||||||
brain.Action = AnimalActions.Wander;
|
brain.Action = AnimalActions.Wander;
|
||||||
brain.TargetPlant = -1;
|
brain.TargetPlant = -1;
|
||||||
var angle = _rng.NextSingle() * MathF.Tau;
|
// Стадность: социальный зверь вдали от группы бредёт к её центру (когезия), рядом —
|
||||||
brain.Target =
|
// мельтешит случайно (с расхождением). Несоциальный — обычное случайное блуждание.
|
||||||
pos
|
var toCenter = herdCenter - pos;
|
||||||
+ new Vector2(MathF.Cos(angle), MathF.Sin(angle))
|
var centerDist = toCenter.Length();
|
||||||
* (_cellSize * (4f + _rng.NextSingle() * 6f));
|
if (
|
||||||
|
sociability >= HerdMinSociability
|
||||||
|
&& herdCount > 0
|
||||||
|
&& centerDist > HerdSeparation * _cellSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
brain.Target =
|
||||||
|
pos
|
||||||
|
+ toCenter
|
||||||
|
/ centerDist
|
||||||
|
* (_cellSize * (3f + _rng.NextSingle() * 3f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var angle = _rng.NextSingle() * MathF.Tau;
|
||||||
|
brain.Target =
|
||||||
|
pos
|
||||||
|
+ new Vector2(MathF.Cos(angle), MathF.Sin(angle))
|
||||||
|
* (_cellSize * (4f + _rng.NextSingle() * 6f));
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,6 +489,42 @@ public sealed class AnimalDecisionSystem : BaseSystem
|
|||||||
private float VisionRadius(int species, float vision) =>
|
private float VisionRadius(int species, float vision) =>
|
||||||
MathF.Max(1f, _animalSet[species].Def.VisionCells) * MathF.Max(0.2f, vision) * _cellSize;
|
MathF.Max(1f, _animalSet[species].Def.VisionCells) * MathF.Max(0.2f, vision) * _cellSize;
|
||||||
|
|
||||||
|
// Центр и размер ближайшей группы соплеменников в радиусе (для когезии/безопасности). O(n) на особь,
|
||||||
|
// как и прочие сканы. Сам исключается по id; центр — средняя позиция соседей того же вида.
|
||||||
|
private void HerdScan(
|
||||||
|
Vector2 from,
|
||||||
|
float radius,
|
||||||
|
int species,
|
||||||
|
int selfId,
|
||||||
|
out Vector2 center,
|
||||||
|
out int count
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var sum = Vector2.Zero;
|
||||||
|
count = 0;
|
||||||
|
var radiusSq = radius * radius;
|
||||||
|
foreach (var (_, _, organisms, _, transforms, entities) in _animals.Chunks)
|
||||||
|
{
|
||||||
|
var oo = organisms.Span;
|
||||||
|
var tt = transforms.Span;
|
||||||
|
for (var i = 0; i < tt.Length; i++)
|
||||||
|
{
|
||||||
|
if (oo[i].Species != species || entities.EntityAt(i).Id == selfId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Vector2.DistanceSquared(from, tt[i].Position) <= radiusSq)
|
||||||
|
{
|
||||||
|
sum += tt[i].Position;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
center = count > 0 ? sum / count : from;
|
||||||
|
}
|
||||||
|
|
||||||
// Ближайший хищник-угроза в радиусе восприятия (для бегства жертвы). Угроза = другой вид, который
|
// Ближайший хищник-угроза в радиусе восприятия (для бегства жертвы). Угроза = другой вид, который
|
||||||
// ест мясо и достаточно крупный (см. IsThreatTo). O(n) на особь — как поиск корма.
|
// ест мясо и достаточно крупный (см. IsThreatTo). O(n) на особь — как поиск корма.
|
||||||
private bool TryFindThreat(
|
private bool TryFindThreat(
|
||||||
|
|||||||
Reference in New Issue
Block a user