Run 12: knowledge system, mortality, and the North's generational soul
SPEC §5 built: three skill domains (forage/woodcraft/masonry) that pay off and grow by practice; proximity diffusion soul-calibrated by absorption x teaching-drive; and mortality - the piece that makes knowledge generational. Elders die (~150-230 days), everything unshared dies with them, a youth comes of age near knowing nothing. North soul (absorption 1.6, teaching 0.9) vs South (1.0, 0.3); East teaches no one. Two findings: (1) headless soaks were contaminated by orphaned child processes writing later runs' CSVs - fixed with StopAfterDays clean self-terminate; (2) passive diffusion barely helped - the North's real culture is the young seeking elders on purpose, so added deliberate teacher-seeking and youths now cradle beside the wisest elder. North now leads knowledge at every checkpoint, but a stable town approaches the ceiling regardless, so teaching is a recovery advantage not an equilibrium one. The crisis-recovery experiment is the real test, noted in FINDINGS as next. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+80
-3
@@ -33,6 +33,13 @@ public class Npc
|
||||
public float Health = 100f; // forced rest below 30
|
||||
public float Nourishment = 1f; // 1 = fed; drains daily, eats from pack
|
||||
|
||||
// --- Mortality: knowledge is generational only if generations end ----
|
||||
public float AgeDays;
|
||||
public float LifeExpectancyDays = 180f;
|
||||
|
||||
// --- Knowledge (SPEC §5) ----------------------------------------------
|
||||
public Knowledge Know = new();
|
||||
|
||||
// --- State -----------------------------------------------------------
|
||||
public NpcState State = NpcState.Gathering;
|
||||
|
||||
@@ -78,6 +85,33 @@ public class Npc
|
||||
/// who they are becoming. Indexed by (int)NpcState.</summary>
|
||||
public int[] StateTicksToday = new int[5];
|
||||
|
||||
/// <summary>
|
||||
/// Learn from another by proximity (SPEC §5). Rate scales with your own
|
||||
/// absorption and their drive to teach. Returns true if anything passed.
|
||||
/// </summary>
|
||||
private bool LearnFrom(Npc other)
|
||||
{
|
||||
bool learned = false;
|
||||
float rate = 0.0016f * Soul.Absorption * (1f + other.Soul.TeachingDrive * 2.5f);
|
||||
for (int d = 0; d < Knowledge.DomainCount; d++)
|
||||
{
|
||||
float gap = other.Know.Skill[d] - Know.Skill[d];
|
||||
if (gap > 0.03f)
|
||||
{
|
||||
Know.Gain((KnowledgeDomain)d, gap * rate);
|
||||
learned = true;
|
||||
}
|
||||
}
|
||||
return learned;
|
||||
}
|
||||
|
||||
/// <summary>Clear live references to someone who has died.</summary>
|
||||
public void ForgetPerson(Npc dead)
|
||||
{
|
||||
if (_targetNpc == dead) _targetNpc = null;
|
||||
if (_giftSource == dead) _giftSource = null;
|
||||
}
|
||||
|
||||
// --- Diagnostics (read-only views for the stats logger) ---------------
|
||||
public MaterialKind CurrentDemand(GameManager gm) => PickGatherTarget(gm);
|
||||
public int RoughNights => _unshelteredRests;
|
||||
@@ -426,8 +460,12 @@ public class Npc
|
||||
else
|
||||
{
|
||||
Activity = $"harvesting {_targetNode.Kind.ToString().ToLower()}";
|
||||
float taken = world.Harvest(_targetNode, requested: 2f, this);
|
||||
// Skill pays: a practiced hand takes more per reach — and the
|
||||
// work itself is the teacher.
|
||||
var domain = Knowledge.DomainFor(_targetNode.Kind);
|
||||
float taken = world.Harvest(_targetNode, requested: 2f * (1f + Know[domain]), this);
|
||||
if (taken <= 0f) { _targetNode = null; return; } // node at its floor — re-seek
|
||||
Know.Gain(domain, 0.0006f);
|
||||
Inventory.TryGetValue(_targetNode.Kind, out float have);
|
||||
Inventory[_targetNode.Kind] = have + taken;
|
||||
|
||||
@@ -645,9 +683,42 @@ public class Npc
|
||||
|
||||
private void TickSocializing(GameManager gm)
|
||||
{
|
||||
// Drift toward the nearest neighbor — community forms where feet do.
|
||||
// The young seek a teacher. A soul that still has much to learn and
|
||||
// the drive to learn it looks for the wisest elder in town rather
|
||||
// than drifting to whoever's closest. This is the North's culture in
|
||||
// motion: the young sitting at the feet of the old, on purpose.
|
||||
Npc? nearest = null;
|
||||
float bestDist = float.MaxValue;
|
||||
|
||||
if (Soul.Absorption > 1.2f && Know.Total < Know.Capacity * 0.6f)
|
||||
{
|
||||
Npc? teacher = null;
|
||||
float bestWorth = 0.15f; // must actually know more than us
|
||||
foreach (var other in gm.Npcs)
|
||||
{
|
||||
if (other == this || other.HomeTown != HomeTown) continue;
|
||||
float worth = (other.Know.Total - Know.Total) * (1f + other.Soul.TeachingDrive);
|
||||
if (worth > bestWorth) { bestWorth = worth; teacher = other; }
|
||||
}
|
||||
if (teacher != null)
|
||||
{
|
||||
if (Position.DistanceTo(teacher.Position) > 3f)
|
||||
{
|
||||
Activity = $"seeking out {teacher.Name} to learn";
|
||||
MoveToward(teacher.Position);
|
||||
}
|
||||
else
|
||||
{
|
||||
LearnFrom(teacher);
|
||||
Activity = $"learning from {teacher.Name}";
|
||||
}
|
||||
if (!gm.IsNight && _stateTimer > 15f) Enter(NpcState.Gathering);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise: drift toward the nearest neighbor — community forms
|
||||
// where feet do.
|
||||
foreach (var other in gm.Npcs)
|
||||
{
|
||||
if (other == this) continue;
|
||||
@@ -659,9 +730,15 @@ public class Npc
|
||||
Activity = $"walking over to {nearest.Name}";
|
||||
MoveToward(nearest.Position);
|
||||
}
|
||||
else if (nearest != null)
|
||||
{
|
||||
Activity = LearnFrom(nearest)
|
||||
? $"learning from {nearest.Name}"
|
||||
: $"chatting with {nearest.Name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Activity = nearest != null ? $"chatting with {nearest.Name}" : "idling";
|
||||
Activity = "idling";
|
||||
}
|
||||
|
||||
// TODO(Week 3): bonds + Generosity-weighted spontaneous help.
|
||||
|
||||
Reference in New Issue
Block a user