Wilden Dawn

Movement direction probability

By Robin Dowling · 5 months ago

Early on in the simulation, I noticed that organisms moved in jagged, discontinuous patterns. Each tick, they would pick a new direction without any memory of where they had just come from. This created movement that looked mechanical and unnatural. Organisms would frequently reverse direction or move in sharp angles, creating zigzag paths across the world.

The solution was to introduce directional momentum. When an organism decides to move, it now considers its current direction and weights the probability of continuing in that direction higher than alternatives. The probability decreases exponentially as the angle from the current direction increases.

Moving in the same direction receives about 42% probability. Adjacent directions at 45° angles receive about 21% each. The opposite direction, a complete 180° reversal, gets only about 3% probability.

This creates a strong preference for continuing along a path rather than constantly changing course. The result is movement that feels more organic. Organisms trace smoother paths, curve gradually, and maintain momentum as they explore or pursue goals.

The system applies to all movement types, not just random exploration. When an organism moves toward food, away from threats, or back to familiar territory, it still considers its current direction in the decision. This prevents the ping-ponging behavior that occurred when organisms would move one step toward a goal, then immediately reverse course the next tick.

It's a simple mathematical weighting mechanism, but it transforms how movement looks and feels in the simulation.

Interactive organism following

By Robin Dowling · 5 months ago

Watching thousands of organisms move across the simulation is interesting, but it can be hard to focus on any individual. I wanted a way for observers to follow specific organisms, tracking their movements and behaviors over time.

Following an organism changes how you experience the simulation. Instead of watching the ecosystem as a whole, you see the world from one organism's perspective. You watch it search for food, flee from threats, find mates, reproduce. You see its children born and follow them as they live their own lives. You witness the micro-decisions that create the macro patterns of evolution.

The solution was to make organisms clickable. Click on any organism and the camera follows it, keeping it centered in your viewport as it moves through the world. Click the background to stop following and return to manual control.

Implementing this required coordination between client and server. When clicking on an organism, the client sends a message to the server identifying which entity to follow. The server maintains a map of which session is following which entity and updates the session's position to match the entity's position before each state broadcast. The client receives state updates with the camera automatically centered on the followed organism.

When an organism dies, the follow automatically ends, controlled from backend. If a client disconnects, the server cleans up the follow mapping. The system handles these edge cases without requiring manual intervention.

The click-to-follow mechanism also laid groundwork for future features. The message protocol includes a control interaction type, currently unused, that could eventually enable direct organism control rather than just observation.

From code to life

By Robin Dowling · 6 months ago

The visualization of life in the evolution simulation has gone through a few stages so far, and will go through many more in the future. Each iteration is an improvement and a step closer to full immersion into the simulated world.

1. Text based

The visualization of simulated world started out as mere statistics about the evolutionary progression, displayed as text in a terminal. While it was exhilarating to witness evolution taking place inside of a computer, it left a lot to the imagination.

2. Moving symbols

The first visual client rendered the entire simulated world as simple geometric shapes - animals drawn as wobbly circles, and plants as triangles. Species were indicated by colors and energy as amplitudes of wobbliness.

The evolution of the world could be seen moving from births to flocks, from migration to extinction. Groups of animals could be seen moving as a single being, murmuring like birds in the sky. Species of plants could be seen taking over the entire world, pushing out other species and taking their place.

3. Actual animals and plants

The second client rendered the exact same data as the others, but in a much more visually pleasing way. Plants look like plants, varied in size, color, shape, and attributes. Animals look like animals, also varied in patterns, shapes, sizes, and behavior. All based entirely on the genetics of each individual organism in the simulation.

4. Future...

The idea for the next client is to render plants and animals in 3D, making the simulated world fully immersive.

The evolution becomes immersive

By Robin Dowling · 6 months ago

I've recently added a major new feature to my evolution simulation project - a dual rendering system with a navigable viewport. This update introduces two distinct ways to visualize the simulated world:

Global view

The original view remains available, showing the entire simulation world at once. This provides an overview with simplified entity representations, suited for monitoring large-scale patterns and population distributions.

Rich view

The new rich client offers a limited rectangular viewport showing only a portion of the world at any time. This scoped view enables:

  • Detailed organism rendering with segments, patterns, and dynamic visual traits
  • Amplitude-based wobble effects for environmental conditions
  • Cross patterns with wobble effects for resource elements
  • Enhanced color generation based on species traits

Most importantly, users can now navigate through the world using arrow keys, moving their viewport to explore different regions of the simulation as it evolves. This is what will become the actual game players will play.

Technical implementation

Behind the scenes, this update required significant architecture changes:

  • Created a dual client state system with appropriate viewport handling
  • Implemented separate drawable classes for minimal and rich entity rendering
  • Added user input handling for movement
  • Developed canvas coordinate transformations for viewport display
  • Rendered oragnisms closer to actual animals and plants - with much more to be done there

The result is a more immersive and interactive experience that allows users to explore the evolution simulation in unprecedented detail while maintaining the option for a global overview.

Mapping genetics to organism traits

By Robin Dowling · 6 months ago

Next step is to make the organisms actually look interesting. The wobbly circles was mostly for me as a developer to see what was happening in the world, but it doesn't make for an exciting world to look at. At least not compared to having actual animals and plants.

Again, the organisms all have a varied genetic composition. Each organism is most likely unique, just like each human most likely is. Some organisms have a genetic composition similar enough for them to be seen as the same species, or the same genus etc.

Mapping genetics to visible traits

I mapped the genetic composition of each organism to different granular traits of animals and plants respectively. For example, high values of calcium and phosphor make the organism more likely to have bigger teeth, while high values of iron, potassium and sulphur might create a zigzag patter on a plants leaves.

With a large number of mappings to granular traits, I then render and animate the complete organism, based entirely on the genetics, resulting in a wide variety of animals and plants, all evolved organically in the simulated evolution.

Genetics affect behavior and appearence

These animals fight each other, form groups with their own, eat plants and other animals, sometimes after fighting them to death. They find suitable mate for reproduction, threaten rivals, have offspring, teach them which plants to eat and which to stay away from, and where to find them. They multiply across generations, and their species mutate over time, making way for new species.

All based on their genetics. Now it's also starting to become visible. More details will emerge over time.

Fear, as a way of keeping species together

By Robin Dowling · 7 months ago

In the evolution simulation, I’ve introduced a system that models fear-driven behavior to promote grouping among motile organisms. Each organism continuously evaluates its surroundings to determine its level of fear, shaped by both its social context and environment.

Fear is reduced when nearby organisms of the same species are present—especially in larger numbers. Conversely, fear increases over time when the organism is isolated or surrounded by groups of other species. As fear increases, the organism becomes increasingly likely to return to the last known location where it encountered a group of its own kind.

This creates a soft pressure for organisms toward staying near their own kind, and away from others . Organisms that leave a large group but remain close to a few peers experience only a mild increase in fear, allowing smaller offshoots to explore independently and eventually rejoin the group. The result is emergent flocking behavior: groups form, fragment, and reassemble through fully decentralized decisions. There is no leader or controller—each organism acts based on its own. The overall movement resembles bird murmurations, where coordination emerges without any top-down mechanism.

Over time, this dynamic encourages stable group formation and adaptive movement across the landscape, driving species-level divergence and more complex ecological patterns in the simulated world.

Lifelike movement

By Robin Dowling · 9 months ago

In the evolution simulation, groups of organisms often move together—shifting, splitting, and reforming in fluid patterns that resemble bird murmurations. There’s no leader, no shared objective, and no explicit rule to move as a group. Yet the behavior emerges naturally from decisions made by each individual.

Each organism evaluates its surroundings and decides whether to move based on its current individual needs: eating, reproducing, fleeing danger, or just exploring. If it’s low on energy and remembers a location where energy was once found, it moves in that direction. If it’s looking for a mate, it looks for nearby compatible organisms or moves toward places where it has seen others of its kind.

Organisms also prefer to stay close to others of their own species—especially when resting or reproducing—so they tend to cluster. But occasionally, one ventures off. If that organism finds something valuable, others nearby might gradually follow, drawn by the same logic, not by any awareness of the movement as a group.

What emerges is lifelike: organisms migrating across the world, forming temporary herds or colonies, and collectively shifting direction without coordination. The group behavior is entirely emergent, built from individual logic and memory—no central control, no global plan.

Plant pollination and hot/cold states

By Robin Dowling · 10 months ago

Problem: simulating plants or other sessile organisms pollinating and mating with others of the same species over distance.

A straightforward approach was to check for same-species organisms at adjacent positions. This mirrors pollen spreading by wind and was convenient because I already had the functionality for adjacent position searches. The drawback was that it put more pressure on an already tight loop with little FPS headroom.

After weighing alternative designs and their complexity against possible efficiency gains, I kept the adjacent-position search but added two under-the-hood improvements. I wanted to cache adjacent data per position and species, but that required a stable state to cache. Since my state changed constantly by design, a cache would go stale immediately.

Solutions

  1. Double buffer read/write state
    Previously, I used a single “hot” state for both reads and writes, meaning the state changed constantly during iteration. This worked because I ensured each organism was processed only once per iteration, but it made caching impractical.

    The fix was to implement a double-buffered state manager with two parallel states. One state is read-only for the duration of the iteration. All updates are written to the second state, which is rebuilt in the background. At the end of the iteration, the states swap, so the next iteration reads from the previously written state while a new one is built from scratch.

  2. Cache
    With a static read-only state, I could implement a simple key-value cache for lookups, such as adjacent positions. This allowed plants to locate nearby same-species plants and attempt mating efficiently.

Evolution of Server-Client Communication

By Robin Dowling · 10 months ago

Building an efficient server-client communication system for my evolution simulation has been a fascinating journey. Each iteration brought new challenges and insights as I balanced performance, bandwidth usage, and user experience. Here's a look at my progression:

1. Always Full State

My first implementation was straightforward: send the entire world state to every client on each update. I created a data structure that contained all entity information, metadata about the world state, and statistics. This provided a reliable baseline but quickly revealed scalability issues as my simulation grew.

This approach worked well for testing but wasn't sustainable with hundreds of organisms and multiple clients.

2. Custom Serialization

To reduce payload size, I implemented custom serialization. Instead of sending verbose JSON with property names, I used a simple array where keys and values are packed together, removing the need for many control chars.

This significantly reduced my payload size, with some entities shrinking from ~200 bytes to just ~50 bytes.

3. Minimized IDs

Entity IDs were initially small 4 char base62 IDs. I implemented a more compact ID system using incremental base62 IDs for session-scoped references, reducing size even more.

4. Change Deltas

Instead of sending complete entities on each update, I began tracking client state and sending only what changed. I implemented a function that compares two states of an entity and returns only the properties that differ between them. This delta approach meant that if only an entity's position changed, I would send just the position rather than the entire entity definition.

This reduced typical payloads by 70-90% in stable world states, as most entities only change position or a few properties between frames.

5. Notify Server of Missing States

Delta-based approaches created a new problem: what if clients miss an update? I implemented a feedback mechanism allowing clients to notify the server when they're missing entity data. When a client sends a feedback message indicating it has missing entities, the server clears its assumptions about what the client knows and sends a full update on the next iteration.

6. Skip Out-of-Order State Iterations

Network delays sometimes resulted in iterations arriving out of sequence. I added a simple mechanism to track the last applied iteration and ignore older ones. In the client code, I check if the received state's iteration number is less than the already applied iteration, and if so, discard it.

This prevented visual glitches where the world would temporarily "jump backward."

7. Client-Side State Buffering

To further address the occasional out-of-order delivery, I implemented a buffering system in the client. The system stores incoming states in a map keyed by iteration number. When processing states, it first looks for the exact next iteration needed. If that iteration is available, it's applied and removed from the buffer. If too many states accumulate in the buffer, it falls back to using the oldest one to prevent memory issues.

This buffer allowed the client to store states that arrived early and apply them in the correct sequence, creating smoother animations.

8. Aggregate Similar Organisms

As my simulations grew more complex, areas with high organism density created bandwidth spikes. A solution was to aggregate similar organisms in congested positions. When detecting multiple organisms of the same species in the same position, I add a special "aggregate" metadata property to the first organism's state, indicating how many similar organisms are at that location.

The client then expands these aggregates into individual entities. It checks for the aggregate property, and if present, creates that many individual entities with slightly modified IDs to distinguish them.

Results

The iterative improvements paid off:

  • Average payload size: reduced from ~1.5MB to ~2KB per update
  • Peak bandwidth usage: reduced from ~10MB/s to ~20KB/s

Each optimization built upon the previous one, creating a data transfer system that's efficient, resilient, and scalable.

Minimal serialization

By Robin Dowling · a year ago

When sending frequent updates in a real-time system, JSON wastes a surprising amount of bandwidth. Every key is wrapped in quotes, separated by colons, and repeated on every update. For small payloads, that overhead can be bigger than the actual data.

For example, a JSON delta update like:

{ "a": "Alice", "b": "30" }
      

is 23 bytes on the wire. By removing quotes, braces, and colons, and by making the first character of each segment the key, the same data becomes:

aAlice,b30
      

— just 14 bytes.

This compact format works best when keys are short and fixed, and values are simple strings. It’s trivial to encode and decode, and in high-frequency updates the savings add up quickly, without needing the complexity of a binary protocol.