Week 8 - Visualization
April 28, 2025
In this week’s entry, we take a step back and examine how far the visualization tools have come. Specifically, we analyze the improvements made between the original visualizer and the upgraded, feature-rich version.
The visualizer isn’t just a cosmetic tool it’s SUPER, SUPER important for understanding how agents behave, how different species interact, and how settings like altruism influence real-time decision-making.
—
The First Visualizer
The first visualizer focused on one thing: basic movement and food interaction for evolved agents.
Some of its main features were:
– Basic loading of brains trained on different radii species.
– Fixed behavior: all agents always played in “food-chase” mode.
– Simple environmental sensing: Agents could detect walls and nearby food.
– Food disappeared immediately when eaten; there was no respawn logic or chasers.
– Consistent species properties: Step sizes and radii were baked in.
– Minimalistic Pygame window with only the environment and agents drawn.
Here’s a glimpse at how a simple agent moved:
def sense_and_move(self, food_list, env_size):
inputs = [
self.x/env_size,
self.y/env_size,
(env_size-self.x)/env_size,
(env_size-self.y)/env_size
]
...
out = self.brain.forward(inputs)
decision = np.argmax(out)
angle = decision * (2*math.pi)/4
self.x += self.step * math.cos(angle)
self.y += self.step * math.sin(angle)
It was clean and functional — but it left a lot of potential for more complex behaviors untapped.
—
The Upgrade
The new version massively expands on the original tool. Here’s how:
1. UI Elements and Controls
– Introduced buttons at the top for toggling altruism, starting/pausing/resetting the simulation, and manually placing agents.
– Buttons actively update their text and status (e.g., “Pause” becomes “Resume”).
– Manual agent placement mode lets you custom-build your environment.
2. Dynamic Altruism Mode
– A simple button toggles Altruism Mode ON or OFF during a live session.
– This setting controls whether food is shared proportionally (altruism) or “stolen” by the largest nearby agent.
if altruism_mode:
size_sum = sum(a.radius for a in fs.eaters)
for ag in fs.eaters:
ratio = ag.radius / size_sum
gain = FOOD_SCORE * ratio
ag.score += gain
else:
max_radius = max(a.radius for a in fs.eaters)
winners = [a for a in fs.eaters if a.radius == max_radius]
if len(winners) == 1:
winners[0].score += FOOD_SCORE
3. Food Mechanics Upgraded
– Food now requires 30 frames of “eating” before it disappears.
– A progress arc is drawn around food to visualize how close it is to being eaten.
4. Chasers Added
– Chasers actively hunt down agents.
– If a chaser catches an agent, that agent suffers a fitness penalty and a freeze indicator flashes on them for one second.
– Chasers have “frozen” states after catching someone, visualized with cooldown timers.
5. Species Stats and Scoring
– The UI displays live species stats, showing how many agents of each species exist and their average score.
– Dynamic tracking of food counts and active chasers keeps the player informed.
6. Realistic Neural Variation
– Agents now load from a master neural network with slight random variance added to weights, so no two agents behave identically.
def _add_variance(self, state, variance):
modified_state = {}
for key, weight in state.items():
variation_factor = 1.0 + (2 * random.random() - 1) * variance
modified_state[key] = weight * variation_factor
return modified_state
7. Better Movement and World Rules
– Agent sensing includes walls, food, other agents, and chasers — a full 11-feature input vector matching training conditions.
– Agents are penalized for bumping into walls — just like during training — to discourage reckless behavior.
—
We talked about the altruism gene before, now it’s more refined and we can go into more detail!
Altruism in the New Simulation
The simulation has been upgraded to allow the altruism gene to meaningfully influence the agents’ evolution. Let’s talk about how it works and why it’s important.
At every frame, agents can move toward food, avoid chasers, and interact with each other. But how they collect food is where altruism or selfishness (stealing) comes into play.
When food is eaten, the simulation checks if altruism_enabled is active:
if altruism_enabled:
size_sum = sum(SPECIES_RADII[pi] for pi, _ in fs.eaters)
for pi, idx in fs.eaters:
ratio = SPECIES_RADII[pi] / size_sum
gain = FOOD_SCORE * ratio
self.populations[pi].fitness[idx] += gain
else:
sizes = [SPECIES_RADII[pi] for pi, _ in fs.eaters]
maxr = max(sizes)
winners = [(pi, idx) for (pi, idx), r in zip(fs.eaters, sizes) if r == maxr]
if len(winners) == 1:
pi, idx = winners[0]
self.populations[pi].fitness[idx] += FOOD_SCORE
Key Differences Depending on Altruism:
Altruism ON:
All agents touching the food share it based on their body size.
Larger agents get more, but even the smallest agents get some reward.
This promotes cooperation among different species.
Altruism OFF (Stealing Mode):
Only the single largest agent gets all the food.
If multiple large agents are tied, nobody gets it (food is wasted).
This creates a cutthroat environment where size dominance is king.
Important Nuances
1. Size Matters for Sharing
The amount of food each agent gets is proportional to its radius when altruism is enabled. This means larger species still benefit more, but without outright destroying the smaller ones. It keeps the ecosystem diverse longer.
2. Evolution Is Mode-Sensitive
Because fitness directly depends on how food is distributed, agents evolve very differently:
Under altruism, you might evolve agents that are good at finding food and cooperating.
Under stealing, bigger and greedier agents dominate.
3. Food Takes Time to “Mature”
Food isn’t collected instantly — agents must “stay” over it for 30 frames. This gives time for multiple agents to arrive, making altruism even more meaningful because contests aren’t decided immediately.
4. Chasers Add External Pressure
Chasers hunt down agents and freeze them temporarily. This outside threat makes food collection risky, encouraging faster movement and smarter group dynamics.
5. Neural Networks Are Batched
Each agent has its own personal brain (BatchNeuralNetwork), but inference is done in a highly efficient batched way: x = F.relu(torch.bmm(x, self.w1)) This ensures that even with 300 agents, simulation speed remains high.
x = F.relu(torch.bmm(x, self.w2))
...
6. Evolution Is Competitive
At the end of every generation, only the top ~10% of agents survive to copy their brains to the next generation. Mutation ensures slight randomness to allow new strategies to emerge.

Leave a Reply
You must be logged in to post a comment.