LOG_002 · DEVLOG · GodSim · August 2026

Bringing the World
to Life

Hunger, a utility AI, drifting personalities, and the first roles nobody assigned

Last post I built the world — an empty stage. This post adds the people: a tribe of units that all start out nearly the same, with no scripted behavior and no assigned jobs.

Every tick, each unit checks its own hunger, energy, and personality, gives every possible action a score, and does the one that scores highest. That single rule is enough for units to gradually specialize — one becomes a forager, another a scout, another an elder — without any of it being written by hand. The rest of this post explains how that works, one system at a time.

01 · The Substrate

Three Numbers to Live By

Every unit has three stats — Hunger, Energy, and Health — and nothing forces it to stay alive. A unit that keeps making bad choices can starve to death.

Hunger drops steadily while a unit is awake. Energy drops while it's active and recovers while it rests or sleeps. Health only starts falling once Hunger is already empty. Importantly, these stats don't override the unit's decisions — they're just numbers that feed into its scores. So a unit that keeps choosing to socialize instead of eat really can die. That's intentional: if units couldn't die from bad decisions, the God's interventions later in the series wouldn't matter. And when a unit dies, any resource locations it found but hadn't reported yet are lost with it.

The stat math

All the rates are set in the SimConfig asset: Hunger falls 0.5/sec, Health falls 0.8/sec while starving, Energy falls 0.5/sec when active and recovers 2.5/sec asleep. Health only drops once Hunger is at or below 15:

if (Hunger <= C->HealthDrainHungerThreshold)
    Health = FMath::Clamp(Health - C->HealthDrainRate * DeltaSeconds, 0.f, 100.f);
02 · The Engine

Score Everything, Pick the Winner

There's no fixed priority order. Every tick, a unit scores all nine of its possible actions and does the highest-scoring one. A little randomness is added so two identical units don't always choose the same thing.

Foraging 72 ← wins Wandering 48 Depositing 35 Socializing 31 Reporting 24 Resting 20 Gathering 15 Sleeping 12 Emergency-eat gated off — unit isn't hungry enough

Each bar is one action's score for a single unit on a single tick. Foraging is highest, so the unit forages. Because Wandering is close behind and random noise is added to every score, the same unit could just as easily wander instead. Emergency-eat scores nothing here because it only turns on when hunger is low.

That randomness is also a tuning dial. More of it makes the tribe behave more unpredictably; less of it makes units act more like pure optimizers. A small amount keeps behavior varied without turning it into chaos.

The scoring loop

Each action's score gets random noise (up to ±12) added, and the highest total wins. No action is treated as special. Adding a new behavior means writing one score function and one line to include it — it then competes with everything else automatically:

auto Eval = [&](ESimUnitState Action, float Score) {
    Score += FMath::RandRange(-Noise, Noise);
    if (Score > Best) { Best = Score; Winner = Action; }
};
Eval(ESimUnitState::Foraging,  ScoreForage());
Eval(ESimUnitState::Wandering, ScoreWander());
// ...seven more...
ExecuteAction(Winner);
03 · Personality

Why Identical Units Diverge

Each unit is born with five personality traits that shift its scores, and those traits slowly change based on what the unit actually does.

The traits are Curiosity, Industriousness, Sociability, Prudence, and Piety. A curious unit scores Wander higher; a prudent one eats before it gets dangerously hungry. Every time a unit acts, the trait behind that action rises a little, and the traits it didn't use drift back toward the values it was born with. So a unit that forages a lot becomes more industrious, which makes it score foraging even higher, which makes it forage more often. Over a long run, that feedback is what turns a group of near-identical units into distinct individuals.

Inspect panel for Unit 43, a Generalist foraging food: Industriousness at 0.73, the highest of its five traits
High Industriousness
Inspect panel for Unit 38, a Generalist wandering to explore: Curiosity at 0.89, the highest of its five traits
High Curiosity

Two units from the same run. Both started with every trait at 0.5. The one on the left has done a lot of work and drifted toward high Industriousness; the one on the right has explored a lot and drifted toward high Curiosity.

How drift works

The trait behind the chosen action goes up; the unused traits move back toward the unit's original SeedTraits values. That means a trait only stays high while the behavior keeps happening:

*Expressed = FMath::Clamp(*Expressed + C->TraitDriftRate, 0.f, 1.f);
auto Regress = [C](float& T, float Seed) { T += (Seed - T) * C->TraitRegression; };

Each action maps to one trait: wandering to Curiosity, foraging/gathering/depositing to Industriousness, socializing/reporting to Sociability, and sleeping/resting/emergency-eating to Prudence.

04 · The Economy

Gather, Carry, Stockpile

Units eat at a resource node, then carry the extra back to shared storage. Depositing raises a unit's reputation; hoarding a full load, or taking from storage without real need, lowers it.

Each storage depot holds only one resource type, so you can tell at a glance what the tribe is running low on. Depots also act as an emergency food supply: a starving unit with no food nearby will draw from storage. But taking from storage when it isn't truly desperate costs reputation, which keeps the shared supply available for genuine emergencies.

When is a storage run worth it?

ScoreDeposit() weights the trip by the square of how full the unit is, so a nearly empty pack barely counts and a full one strongly pulls the unit home — units finish gathering before making the trip. Deposit() returns how much the depot actually took in, so a unit doesn't lose surplus when the depot is full:

float Before = StoredAmount;
StoredAmount = FMath::Min(StoredAmount + Amount, Capacity);
return StoredAmount - Before; // the caller keeps whatever wasn't accepted
05 · Knowledge

The Map Isn't Shared

Units don't know where anything is at the start. A resource only becomes known to the tribe after a unit finds it and reports it at the town center, and that shared knowledge fades over time.

A resource node is invisible to the tribe until a unit physically gets close to it, and even then only that one unit knows until it walks the information back to the town center. Each entry in the shared map loses confidence as time passes and is dropped once it hits zero, so the tribe has to keep re-confirming where things are. If a unit finds a good spot and then dies before reporting it, that location is simply lost until another unit happens to find it again.

Inspect panel for Unit 34, a Scout: state is Reporting -- heading to town center to report, with a path line running from the unit to the Town Center

This Scout found something while wandering and is now walking the discovery back to the town center to report it. Until that walk finishes, no other unit knows it exists.

The Town Center's inspect panel reading 'Discoveries: 5 reported', with a debug overlay showing five glowing wireframe spheres scattered across the map, each connected back to the Town Center by a line

This is what the shared map actually holds after a run: the Town Center's own inspect panel, with a debug overlay drawing a line from itself to every node that's been reported so far. Five discoveries in, five lines out — nothing else on the map exists to the tribe yet.

Confidence decay

A report sets an entry's confidence to full; every tick it erodes, and entries that reach zero are removed:

for (FResourceMemoryEntry& E : Entries)
    E.Confidence = FMath::Max(0.f, E.Confidence - ConfidenceDecayPerHour * GameHoursDelta);
Entries.RemoveAll([](const FResourceMemoryEntry& E) { return E.Confidence <= 0.f; });
06 · Identity

Roles No One Assigned

Units gain skills by doing tasks. When one skill clearly dominates, the unit is automatically given a matching job title: Forager, Gatherer, Scout, or Elder.

A unit that spends its early time near food builds up its foraging skill, which raises its forage scores, which leads it to forage more — until it clearly qualifies as a Forager. The title comes from what the unit has actually been doing, not from anything set in advance. A unit that never specializes in one thing stays a Generalist.

Inspect panel for Unit 18, labeled Forager: carrying 80 wood to storage, high Industriousness at 0.94, foraging skill maxed at 100
Forager
Inspect panel for Unit 26, labeled Scout: wandering to explore, moderate Curiosity and Sociability, exploring skill maxed at 100
Scout
Inspect panel for Unit 25, labeled Elder: socializing near two other units at the town center, socializing skill maxed at 100
Elder

Three units from the same run, each inspected mid-task: a Forager hauling wood home, a Scout wandering to find new ground, and an Elder bonding with others at the town center. Each label tracks whichever skill that unit has actually maxed out.

The dominance test

A unit only earns a title if its best skill group is above a minimum (15) and at least 1.5× higher than its next-best group. Otherwise it stays a Generalist. Adding a new profession is one row in the table:

{ "Foraging",    "Depositing", "Forager"  },
{ "Exploring",   "Reporting",  "Scout"    },
{ "Socializing", NAME_None,     "Elder"    },
// ...
if (BestLevel < Threshold || BestLevel < SecondBest * Dominance)
    return FName("Generalist");
07 · The Artery

Why the Tribe Needs Elders

Elders spread information. An Elder shares what the tribe currently needs with nearby units, so units working far from the town center don't fall out of date.

The shared map only updates at the town center, so a unit working at the edge of the map slowly loses track of where the food is. An Elder — a unit that has specialized in socializing — periodically passes its up-to-date picture of the tribe's needs to any nearby unit whose information is older. The Elder becomes important because of the role it grew into, not because it was handed any authority.

The broadcast rule

An Elder only overwrites information that is older than its own, and it passes along the real age of that information rather than resetting it to brand-new:

if (Other->TribalPressureAge <= TribalPressureAge) continue; // they already know as much
Other->TribalPressure    = TribalPressure;
Other->TribalPressureAge = TribalPressureAge; // keep the real age
08 · Night

Night Splits the Tribe

When night falls, sleeping becomes an option — but only an option. Tired units head home to rest, while others stay out and keep working.

During the day, sleep isn't on the table at all. At night it becomes another scored action competing with everything else — and only for units that have been given a house to sleep in. A tired, cautious unit scores sleep highly and heads home to recover its energy. A unit that's still hungry keeps foraging straight through the night. A unit with plenty of energy left, or little caution, might simply keep working or wandering in the dark. So night doesn't flip a switch that sends everyone to bed; it shifts the balance of scores, and the tribe naturally splits into the ones who rest and the ones who stay out. As new roles come online later in the series, that divide gets richer — a warrior, for example, might stay up to keep watch while the rest of the tribe sleeps.

The tribe's clearing in full daylight, brightly lit with long shadows The same clearing as the light warms further and shadows stretch out The same clearing dimming toward dusk, lit in warm reddish tones The same clearing at night, dim and cool-toned with streaks of light through the trees The same clearing at its dimmest, in flat, cool moonlight

The same clearing, five points across the day/night cycle, crossfading on a loop — from full daylight down to the dimmest, coolest night light and back again.

The twilight hand-off

A cosine curve gives the daylight amount. The moon's strength is a SmoothStep that stays at full until the sun is well up, so the two lights cross-fade with no dark gap between them:

const float SunHeight = FMath::Sin((TimeOfDay - 6.f) / 24.f * 2.f * PI);
const float MoonAmt   = 1.f - FMath::SmoothStep(-0.1f, 0.35f, SunHeight);
MoonComp->SetIntensity(C->MoonlightIntensity * MoonAmt);

The moon is set up so it does not count as an atmosphere sun light, which is why it lights the ground without brightening the sky.

What's Next

A God in the Machine

The tribe now runs entirely on its own — it eats, explores, remembers where resources are, specializes into roles, and dies. The next step is the God: a large language model that reads the simulation's state every few seconds. It will be able to answer questions about the tribe, point out problems like a coming famine before you notice them, and, when you ask, step in and change something in the world.

Series roadmap
World
Layer
Survival
Loop
LLM
Oracle
Social
Fabric
Deep
Emergence