Liza Shulyayeva

The Ongoing Saga of Snail Mating Decisions

I’ve written about snail breeding here before, at length. However, since I now want snail behavior to go through the brain I had to rework the trigger that led to two snails mating and producing offspring. A few different things had to be prototyped to make this work, especially because in a snail’s mind there is no real way of distinguishing food from another snail without learning from experience and some basic instincts to guide the way.

When I started, snails tried to mate with lettuce leaves. Technically possible, but they should learn that this is a little…ineffective.

Then, the snails just kept overshooting each other when trying to approach. Approaching inanimate objects is easy - they don’t move. However, one snail kept targeting the other snail’s current position, and vice versa, and they ended up in the wrong spot. The solution (for now) was to have each snail target the other snail’s target position. This is kind of dumb, because of course the Snail A can’t read minds and see where Snail B is going, so I’ll have to find a non-hacky better way of doing it by basically seeing if the two snails will cross paths on their way to one another and meet each other in the middle instead.

Anyway, then the snails did reach each other but started biting each other as if the other snail was a food item. This resulted in one snail becoming deathly afraid of the other one and trying to run away (and sometimes biting back). Basically the two snails grew to despise each other and want nothing more than to go in opposite directions.

Then they stopped eating.

Then I created scent signatures for other snails and items. Right now this basically just shows an object’s highest macro or fertility rate. This is placeholder, but now snails can “smell” other fertile objects nearby. In the future they’ll also be able to get hungry for certain macros and favor food items which smell more like the thing they’re hungry for.

I also made a sex drive and food drive, so a snail might try to bite an object instead of mating with it if it smells like carbs and its food drive is higher than its sex drive. This can also be used for the snail’s decision about which object it wants to approach (so, we can use this to apply higher weights to certain inputs).

Then I realized there was no good way to test this efficiently without making it easy to move snails around within the jar (without having to go and change positions in the db each time). This way I could place snails next to the objects I wanted them to interact with or detect easier.

And then…then…well, this happened:

(Obviously I need to have them get tired and/or have their sex drive go down after they mate)

The eggs don’t hatch until tomorrow…and I’m both curious and terrified of what will happen then, because I do not yet have a minimum maturity rate for snail breeding.

Went to My First Laravel Meetup

Last Friday night I attended what was apparently the first ever Laravel Meetup in Sweden. I’m glad I actually made myself go, because I’m not really good with social engagements. I don’t tend to go out much and sometimes end up just not bothering and staying home at the last minute.

Anyway, I’m really glad I went. The meetup was hosted by Snowfire and the main talks were about an introduction to Laravel 5.0 and the Laravel IoC container. Both were super interesting and gave me a lot to think about (and a taste of how much I still have to learn about Laravel and PHP in general).

I don’t think I’m going to update to 5.0 until November at the earliest. I have to mentally prepare myself - I’ve already done so many port experiments, and then the actual full port from vanilla PHP to Laravel 4.2, that I’m not looking forward to another large and complicated migration.

Some crooked pictures of the event:

Laravel 5.0 walkthrough

Laravel 5.0 walkthrough 2

Laravel IoC Container

We finished off the night by looking at some Laravel resources and browsing the codebase.

Laravel GitHub

Event Queues

The brain thing is really getting out of control.

Yesterday I ran into this problem:

Fatal error: Maximum function nesting level of '100' reached, aborting!

What caused it was my dumbness. Basically, I was firing the Bite neuron which called the Bite method in the snail class. Then I was checking if the object the snail was biting was an item or another snail. If it was another snail, it called BiteSnail() in the snail class. Here is where it got screwy. I wanted to have the bite do the following:

  • Send pain to the snail being bitten via a manually inserted input to the other snail’s Tactile sensor. Esssentially I was manually pushing a detected object (the biter) to the other snail’s tactile sensor’s detectedArr. To reflect the bite for now I’m using the input’s “roughness” attribute (though this will change) and setting roughness to 55 (the normal snail’s roughness pain threshold is 50)
  • Have the snail being bitten immediately start checking inputs, since you want the snail to react to the bite. The other reason for this is that the recurring action/input checking event creates a new brain each time it runs for each snail, so if we just pushed that input to the other snail’s brain and then forgot about it expecting it to have a reaction the next minute, that input would be totally wiped the next time the event ran.

The problem though was that I was creating a new snail brain, calling all the same functions that were already running on the current snail. The input checks for the two snails were overlapping and either I had too many recursive function calls or I had an infinite loop somewhere (infinite snail-biting? A snail ouroboros?) because the normal solution - increasing xdebug.max_nesting_level was not working. I decided to implement a queue system instead of having a snail check all inputs from another snail that’s checking all inputs.

So now I’ve got this possibly even dodgier thing going where I have a queue in the event model. The event, in this case the CheckIdleSnailActions event that checks inputs for the snails and makes them do stuff, finishes running. If a snail bites another snail during this time we add another checkAllInputs action to the event’s queue. When the event does the thing it’s supposed to do the first time around and gets to the end, it loops through its queue and runs everything in there as well.

Here’s what we push to the queue (we use the brain of the snail being bitten, with the extra input already saved to that brain’s tactile sensor)

$toPush = [
            'object' => $object->brain,
            'function' => 'checkAllInputs'
        ];
array_push($event->queue, $toPush);

And then, at the end of the event’s runEvent method we do this:

    if ($eventFinished) {
        Log::info ('event finished');
        $this->resetRunTime();
        if (count($this->queue) > 0) {
            foreach ($this->queue as $event) {
                Log::info('about to call ' . $event['function']);
                call_user_func_array(array($event['object'],$event['function']), func_get_args());
            }
        }
    }

One crappy thing (of many) is that currently I don’t have a good place to save the event, so I end up passing it from runEvent to the snail controller that loops through the snails and checks their inputs, to the snail brains that get created. It’s kind of a mess.

I think this weekend I’m going to stop working on new brain stuff and just focus on refactoring all of this.

The Snails Are Eating Again

Back to the brain we go.

I already had snails eating food in jars before, but it was kind of a hack. Each snail would just eat a bite of whatever consumable item was in the jar. When I stupidly decided to try to make a simple snail brain, I knew I’d have to make things like eating fit into it. Today I made a very rough version of something like that.

Each snail has a number of motor neurons in its brain. Out of all the motor neurons, there is one for biting and one for swallowing. So far I am bypassing the swallowing completely and just firing the bite neuron and having the snail bite food and swallow it at the same time. Basically…here’s how it works. This is kind of a brain dump so may not actually make any sense.

Items and snails are now given some properties: luminance, temperature, hardness, roughness. There may be others down the line, but for now that’s it.

I’m also setting some general pain thresholds for the snails (so far all the snails have the same thresholds), as well as totally random default properties (as per above):

    public $luminance = 25;
    public $temperature = 20;
    public $roughness = 5;
    public $hardness = 15;
    public $luminancePainThreshold = 100;
    public $temperaturePainThreshold = 50;

Items are a little more tailored - their individual luminance, temp, etc properties can be set by item template in the db (though right now I’m just using some default values there too).

So basically if a snail sees something with a luminance that’s too high it will not be having such a good time.

Oh, and snails now have a currentMood property. In the memory tables (which are already created but not yet in use) there is a consequenceRating property, which reflects an action’s impact on the snail’s currentMood.

So the main thing you need to bite an item is to actually be close enough to it to touch it. I made a small change to checkSensor() to allow for a maxDistance to be passed:

    public function checkSensor($sensorQual, $maxDistance = 999999) {
    // ...blah blah blah

Also, we set luminance, temp, roughness, and hardness to null by default in the array of detected objects and then specify which attributes to grab for each sensor (because we don’t care about roughness if the snail isn’t touching the thing, for example).

                    $toPush = [
                        'id' => $object->$idString,
                        'idString' => $idString,
                        'inputWeight' => $inputWeight,
                        'distance' => $distance,
                        'luminance' => null,
                        'temperature' => null,
                        'roughness' => null,
                        'hardness' => null
                    ];

So our TactileSensor ends up looking like this:

class TactileSensor extends Neuron
{
    
    public function checkForInput() {
        $maxDistance = 5;
        $detectedArr = $this->checkSensor($this->snail->currentTactileReceptorQual, $maxDistance);
        foreach ($detectedArr as &$detectedObject) {
         //   $controller = null;
            if ($detectedObject['idString'] === 'snailID') {
            //    $controller = $this->snailController;
                $object = $this->snailController->findSnail($detectedObject['id']);
            }
            else if ($detectedObject['idString'] === 'itemID') {
            //    $controller = $this->itemController;
                $object = $this->itemController->findItem($detectedObject['id']);

            }
            $detectedObject['hardness'] = $object->hardness;
            $detectedObject['temperature'] = $object->temperature;
            $detectedObject['roughness'] = $object->roughness;
        }
        $this->detectedArr = $detectedArr;

    }
}

Another change is in setWeights(). Whereas before we’d just add something like 0.05 to the inputWeight if an object had been detected by more than one sensor, now we actually add all the inputs together. If a specific sensor had a maxDistance specified we give it a higher inputWeight by default (2 instead of 1) because it had a tougher criteria to meet (not sure I’ll keep this, since the closest object already gets a boost anyway down the line).

Anyway, then in the decision neuron we decide what to do…right now none of the memory stuff is implemented, so we go straight to a random “curious or not” check:

    public function checkDecision($object, $memories = null) {
        Log::info('checkDecision object: ' , $object);
        if (count($memories) === 0) {
            $rand = Utility::RandomInteger(0,1);
            if ($rand === 1) {
                // Curious. Approach if far away
                if ($object['distance'] >= 5) {
                    Log::info('approaching object1: ' . $object['id'] . ' distance: ' . $object['distance']);
                    $this->approachObject($object);
                }
                else {
                    Log::info('TOUCHING. TRY TO BITE ' . $object['id']);
                    $this->biteObject($object);

                }

            }
            else {
                // Indifferent. Ignore;
            }
        }
    }

approachObject() and biteObject() then fire the biting and moveleft/moveright/moveup/movedown motor neurons.

As you can see it’s still not really that decision-makey. Snails will eventually have other criteria involved in whether they try to bite something or not (that is, they won’t necessarily try to bite an object just because they’re touching it).

That’s it for now…at least they’re eating again.

Websocketing It Up for Snail Movement

Over the past few days I’ve paused work on the brain to tackle snail movement on the client.

I ended up using Latchet, a Laravel-specific extended version of Ratchet. To start with I just wanted to get something running. I think I ended up doing this in a really roundabout way…basically we subscribe to a specific jar topic on the client, then ping the server every 5 seconds to get back the target position of each snail within that jar. The snail then crawls toward this target position. Target pos on the server is calculated using the snail’s currentSpeed and each snail crawls at the same speed in the browser, so in theory by the time the snail reaches its target position on the server it will also have reached the same location on the client…maybe…I hope. We’ll see.

    var jarString = 'jar/' + jarID;

    var conn = new ab.Session(
        'ws://gastropoda.app:1111', // The host (our Latchet WebSocket server) to connect to
        function() { // Once the connection has been established
            console.log('established');
            conn.subscribe(jarString, function(topic, event, jarID) {
                var parsedMsg = JSON.parse(event.msg);
                if (allSnailsArr.length > 0) {
                    for (var i = 0; i < allSnailsArr.length - 1; i++) {
                        if (parsedMsg.snailID === allSnailsArr[i].snailID) {
                            allSnailsArr[i].targetPosX = parsedMsg.targetPosX * jarCapacityWidthPercentage / 100;
                            allSnailsArr[i].targetPosY = parsedMsg.targetPosY * jarCapacityWidthPercentage / 100;
                        }
                    }
                 }
            });

            (function checkPositions() {
                conn.call(jarString, "HI WORLD");
                setTimeout(checkPositions, 5000);
            })();
        },
        
        function() {
            // When the connection is closed
            console.log('WebSocket connection closed');
        },
        
        {
            // Additional parameters, we're ignoring the WAMP sub-protocol for older browsers
            'skipSubprotocolCheck': true
        }
    );
</script>

Then, in JarTopic.php:

    public function subscribe($connection, $topic, $jarID = null)

    {

        $jarController = new JarController();

        $jar = $jarController->findJar($jarID);

        $allSnails = $jar->snails;

        foreach ($allSnails as $snail) {

            array_push($this->connectedSnailsArr, $snail->snailID);

        }

        $this->publish($connection, $topic, "BROADCASTTEST", $exclude = array(), $eligible = array());

    }

    public function call($connection, $id, $topic, array $params)

    {

        $this->broadcastTargetPositions($topic);

    }
    public function broadcastTargetPositions($topic) {

        $snailController = new SnailController();

        foreach ($this->connectedSnailsArr as $id) {

            $snail = $snailController->findSnail($id);

            $msg = json_encode([

                'snailID' => $id,

                'targetPosX' => $snail->targetPosX,

                'targetPosY' => $snail->targetPosY

            ]);

            $this->broadcast($topic, array('msg' => $msg ));

        }

    }

You can’t see it…but they’re moving! When they don’t find anything interesting to approach they stop, and when they sense something they’re curious about they start crawling toward it.

Go your own way

Braaaaaainsss

I appear to have gotten myself into a bit of a mess.

“Make a snail brain/nerve-cluster for your snails,” they said. “It’ll be easy”, they said.

No. Nobody said that. Who would say that?! I got into this mess on my own and now I don’t know if I can dig myself out. I’ve already spent so much effort on this brain thing that it’s too late to turn back.

And it’s not all bad - it’s coming along, and it’s fun to think about this stuff. The thing is, I have no idea what I’m doing. I’m just making up how I think a snail brain should work. I’m afraid at some point, probably when I’m very far in, I will realize that I’m doing everything all wrong and have to start from scratch.

Snails have a very primitive brain. They are capable of associative learning. This is what I want to simulate.

A couple of nights ago I talked through the parts of the brain I have so far with David pretending to listen. It helped a lot, since when I got back to snailing after js13kGames I had forgotten where I left off or wtf any of what I’d written meant. Now, as a refresher, I’m going to go over it again here.

Typing this up I can already see a bunch of stuff I need to fix, or scrap completely. I guess doing this does help.

The brain

Each snail has a brain:

    protected function getBrainAttribute() {
        $brain = new Brain($this);
        return $brain;
    }

In the brain constructor, we tell the brain which snail it belongs to (since it is not a Model we can’t use the usually handy belongsTo relationship). We also create the olfactory, optical, tactile, and gustatory sensors and what I’m calling the attention and recognition neurons.

    function __construct($snail) {
        $this->snail = $snail;
        $this->olfactorySensor = new OlfactorySensor($snail);
        $this->opticalSensor = new OpticalSensor($snail);
        $this->tactileSensor = new TactileSensor($snail);
        $this->gustatorySensor = new GustatorySensor($snail);
        $this->attentionNeuron = new AttentionNeuron($snail);
        $this->recognitionNeuron = new RecognitionNeuron();
    }

Each sensor is a child class of Neuron.

Every minute a job that checks for recurring events runs. One of these recurring events runs checkAllInputs() within the brain.

    public function checkAllInputs() {
        $this->olfactorySensor->checkForInput();
        $this->opticalSensor->checkForInput();
        $this->tactileSensor->checkForInput();
        $this->gustatorySensor->checkForInput();
        $this->setWeights();
    }

The checkForInput() function then does this. We pass the snail’s current olfactory receptor quality into the checkSensor function.

class OlfactorySensor extends Neuron
{
    
    public function checkForInput() {
        $detectedArr = $this->checkSensor($this->snail->currentOlfactoryReceptorQual);
        $this->detectedArr = $detectedArr;
    }

}

We check the sensor like this in the main Neuron class.

    public function checkSensor($sensorQual) {
        // Create a new jar controller and get all objects inside the jar that the snail is in.
        $jarController = new JarController();
        $allObjectsInJar = $jarController->getAllObjectsInJar($this->snail->jar->jarID);
        
        // Create a blank array of detected objects.
        $detectedArr = [];

        // Set point1 to the snail's current position from the db.
        $point1 = [
            'x' => $this->snail->posX,
            'y' => $this->snail->posY
        ];

        // Loop through each object in the jar and get its position.
        foreach ($allObjectsInJar as $object) {
            // Set point2 to object's current position from db.
            $point2 = [
                'x' => $object->posX,
                'y' => $object->posY
            ];    

            // Get the distance between point1 (the snail) and point2 (the object).
            $distance = Utility::getDistance($point1, $point2); // in cm

            // If the distance is less than or equal to the sensor quality, object is detected 
            if ($distance <= $sensorQual) {

                // What KIND of an object is it? Another snail? Item? 
                // Because each of these have their own tables in the DB and their own primary key columns we need to get this information. 
                // We do this by using the name of the object class and appending "ID" to it. 
                // This is the primary key column name.
                $idString = strtolower(get_class($object)) . 'ID';
                // If the ID of the object === ID of the snail that has detected it,
                // continue through that iteration of the loop and do nothing futher, since we don't need the snail to detect itself.
                if ($object->$idString === $this->snail->snailID) {
                    continue;
                }

                // Otherwise, create an array with the details of the object we're detected. 
                $toPush = [
                    'id' => $object->$idString,
                    'idString' => $idString,
                    'inputWeight' => 1,
                    'distance' => $distance
                ];
                array_push($detectedArr, $toPush);
            }
        }
        return $detectedArr;
    }    

The next step in the checkAllInputs() function is setWeights(). This is where all kinds of screwed up stuff happens:

    protected function setWeights() {
        // Create an array of all sensors we care about
        $allSensors = [
            'olfactory' => $this->snail->currentOlfactoryReceptorQual,
            'optical' => $this->snail->currentOpticalReceptorQual,
            'tactile' => $this->snail->currentTactileReceptorQual,
            'gustatory' => $this->snail->currentGustatoryReceptorQual
        ];

        // Create blank array of detected IDs
        $allDetectedIDs = [];

        // Get index of highest value in array and value itself
        $bestReceptors = Utility::doubleMax($allSensors);

        // Best receptor name is receptor + Sensor. 
        // Note: why not just name them with Sensor already appended in allSensors array?
        $bestReceptorName = $bestReceptors['i'] . 'Sensor';
        Log::info('best receptor array: ' , $this->$bestReceptorName->detectedArr);
        // Loop through all objects detected by best receptor by reference and add 0.5 to their input weight
        foreach ($this->$bestReceptorName->detectedArr as &$object) {
            $object['inputWeight'] += 0.5;
        }

        // Merge objects detected by all sensors into one array
        $allDetected = array_merge($this->olfactorySensor->detectedArr, $this->opticalSensor->detectedArr, $this->tactileSensor->detectedArr, $this->gustatorySensor->detectedArr);
        
        // Create blank array for deduped detected object list
        $allDetectedDeDuped = [];
        // Count occurrences of idential ID in array 
        $countedArr = array_count_values(array_map(function($value){return $value['id'];}, $allDetected));
        // Loop through array of counted values
        foreach ($countedArr as $objectCount) {
            // Loop through all detected objects
            foreach ($allDetected as &$object) {
                // Add 0.1 for each occurrence (so if object is detected by multiple sensors, its inputWeight is higher)
                $object['inputWeight'] += 0.1 * $objectCount;
                
                $count = 0;
                // Loop through all detected deduped array
                foreach ($allDetectedDeDuped as $dedupedDetectedObject) {
                    // If object id already exists, add to count
                    if ($object['id'] === $dedupedDetectedObject['id']) {
                        $count++;
                    }
                }
                // If count is 0/object isn't already in there
                if ($count === 0) {
                    // Push to array of all detected deduped
                    array_push($allDetectedDeDuped, $object);
                }
            }
        }

        /**** whaaaaaaaattttttt??! *****/


        // Get closest object
        $closestObject = null;

        // Loop through all detected deduped array
        foreach ($allDetectedDeDuped as $object) {
            // If closest object hasn't yet been set...
            if ($closestObject === null) {
                // Set this object as closest object
                $closestObject = $object;
            }

            // If distance to this object is less than distance to current closest object...
            if ($object['distance'] < $closestObject['distance']) {
                $closestObject = $object;
            }
        }

        // Loop through deduped...wait again?
        foreach($allDetectedDeDuped as &$object) {
            // If this is the closest object, add to inputWeight
            if ($object['id'] === $closestObject['id']) {
                $object['inputWeight'] += 0.05;
            }

            // Create new synapse for this impulse
            $synapse = new Synapse($object['inputWeight'], $this->attentionNeuron, $object);

        }

        // Pick object to focus on
        $this->attentionNeuron->chooseFocus();
    }


And here is what we have in the Synapse constructor:

    function __construct($inputWeight, $destinationNeuron, $object = null ) {
        $this->inputWeight = $inputWeight;
        $this->destinationNeuron = $destinationNeuron;
        $this->targetObjectID = $object['id'];
        $this->targetObjectClass = $object['idString'];
        $this->targetObjectInputWeight = $object['inputWeight'];
        $this->sendImpulse();
    }

sendImpulse():

    private function sendImpulse() {
        $this->destinationNeuron->receiveImpulse($this);
    }

In this case the destination neuron is the attention neuron.

    public function receiveImpulse($impulse) {
        $object = [
            'id' => $impulse->targetObjectID,
            'idField' => $impulse->targetObjectClass,
            'inputWeight' => $impulse->targetObjectInputWeight
        ];
        array_push($this->allObjects, $object);
    }

After all this is done, and the attention neuron has a list of objects with their IDs, ID column name, and inputWeight, we try and pick which object the snail will focus on.

    function chooseFocus() {
        $focusedObject = null;
        foreach ($this->allObjects as $object) {
            // If there is no focused object, set the first one to focused
            if ($focusedObject === null) {
                $focusedObject = $object;
            }
            // If the focused object inputWeight < current object inputWeight...
            if ($focusedObject['inputWeight'] < $object['inputWeight']) {
                // Set current object as focused
                $focusedObject = $object;
            }
        }
        // Focus chosen, send to recognition neuron
        $brain = $this->snail->brain;
         $brain->recognitionNeuron->checkRecognition($focusedObject);
    }
}

And that’s sort of where I left off on the brain side so far:

class RecognitionNeuron extends Neuron
{

    function checkRecognition($object) {
        Log::info('checking if object id ' . $object['id'] . ' is recognized');
    }

}

The memory tables

Right now I am preparing the memory tables. There will be 3:

  • memories_sensory - for sensory memories, kept for just a few minutes
  • memories_st - for short term memories. If an object collects n sensory memories it will be stored in short term memory
  • memores_lt - for long term memories. This never gets deleted, but memories get weaker as they get older (but they can be refreshed)

So far I only have the sensory memory table, which currently consists of the following columns:

  • memoryID
  • snailID
  • objectID
  • objectIDField
  • objectProximity
  • neuronFiredID
  • consequenceRating

I also have a table of motor neurons, which is what I’ll specify in neuronFiredID. It is basically the action the snail took in relation to the memory. So far it just consists of two columns:

  • neuronID
  • name

And the motor neurons in there so far are:

  • move_left
  • move_right
  • move_up
  • move_down
  • bite
  • mate
  • touch
  • swallow

The memory models

I’ve also created a memory model class, with child classes for sensory, short term, and long term memories. Each memory has a belongsTo Eloquent relationship with a snail. A snail hasMany memories. And of course there is a MemoryController. This is pretty much where I left off.

Like I said - I have a feeling I’m going to look at this in the morning and decide that it’s all wrong and needs to be scrapped. But I guess even if that’s the case, it will be a lesson learned and the next version will be better.

And We’re Back

I took a one-month break from Gastropoda to participate in the JS13kGames challenge with a tiny game called Breaking Brad

But now it’s back to simulations! It’s taking me a while to remember where exactly I left off with Gastropoda last month, but now I think it’s time to figure out how to calculate a snail’s position within a jar. This is important because each snail will detect other objects (items, snails, etc) differently based on its senses. I have to have an actual location for both objects and snails. Right now I’m setting a position randomly when a snail is hatched in a jar, but I have to decide how I’m going to update this position as it crawls. Currently snails crawl at random on HTML5 canvas - they are already starting their crawl from their DB position, but pick direction randomly and then move back and forth across the screen using their speed attribute. So far all I have are some random notes:

  • User actions while watching rendered snails (ie drag and drop, etc) have to influence thes snail’s position in the db, but only the jar’s owner can move snails around.
  • Do I update the general crawl position on the client and then send that data to the server every x seconds? This wouldn’t really work because more than one user can be watching a snail at any one time, so if we take the random turn direction etc and record it, you will get all kinds of dodgy results in the db. Also I’d then need a separate solution for snail movement when the snails are not being rendered at all. Snails do things/move continuously - in this case a tree that falls in a forest does make a sound even if no one is there to hear it. I think I have to set direction or target position on the server and then have the client reflect this if a user is watching a jar, not the other way around.
  • How do I make this scalable to potentially hundreds/thousands of snails?

Today I’m sick and not thinking straight, so I’m not actually going to even try implementing anything at this point. Back to hot tea for me :(

Pathfinder: Two Heroes Are Born

It was a chilly summer evening when Valeros the valiant fighter and Merisiel the super cool rogue walked through the gates of a tiny abandoned farmhouse. They were heroes, you see, and here to rescue the people of the land from a gang of notorious thieving wanderers - the Sczarni - who have been terrorizing Sandpoint under the leadership of a punk named Jubrayl Vhiski.

When the townsfolk put out an ad for two brave heroes to take care of the problem Valeros and Merisiel felt it was unnecessary to mention their lack of experience in adventuring. But now the creepy creaking gate and the menacing-sounding wind was making Merisiel second-guess their decision.

But they pressed on. Merisiel had her dagger and darts at the ready and Valeros had his…some other weapon…also at the ready as they tiptoed down the path to the house. Suddenly, a Mercenary jumped out of the nearby apple tree and kicked Merisiel to the ground! She recovered and threw a dart at his face. It missed, so she threw it away and tried again. Got him! Merisiel kept his eye as a souvenir - and his elite throwing axe. Unfortunately she wasn’t that great with proper weapons, so as she practice-swung the axe around in victory it nicked the side of her thigh. Worth it, though.

Valeros was the image of heroism as he, emboldened by their defeat of the Mercenary, kicked open the door of the farmhouse and strolled inside. That’s when the siren leaped out at his face from the rafters. He slashed it with his short sword, but she swiped one wing into his head and Valeros dropped to the floor, dead.

Merisiel was alone. “I will bring you back, Valeros!” she yelled as the ran away from the farmhouse very quickly. She ran to exhaustion and found herself at a wooden bridge, where a henchman waiting for her! She threw the axe at it, dislocating her shoulder in the process. But it worked. The henchman perished and she secured the entire location. She fought monsters across the land, hoping to stumble into anything that could bring Valeros back to life. She journeyed from the bridge to the waterfront and then to the woods with no luck. Close to death, she managed to barely escape a Giant Gecko when suddenly she realized…He’s not dead! He’s just unconscious!

She ran over to the farmhouse to wake up Valeros, who was very glad to find out that he wasn’t dead after all and just napping for seven hours or so while Merisiel was beating up monsters and henchmen on his behalf with her massive axe. Together they stole a couple of farm horses and rode over to the waterfront, where Jubrayl Vhiski himself was waiting for them! Valeros, well rested from his long nap, took on the gang leader. Unfortunately, he got away. The gang leader, not Valeros. However, at least they got to secure another location.

“We’ve got to split up,” said Merisiel. “We have two locations left - the farmhouse and the woods. If one of us runs into Vhiski again, the other can secure the remaining location.”

It was decided that Merisiel would take the woods (since she’s an elf and all) and Valeros would go back and finish clearing the farmhouse. Vhiski had to be hiding in one of those places.

Except it just so happened that the siren wasn’t done with Valeros. She lunged at him again when he arrived at the house. Only wisdom could defeat her and neither Merisiel nor Valeros had much of that going for them (though let it be known that Merisiel had a little more). Merisiel helped Valeros escape from afar, but the siren herself was not defeated. She was still out there…hiding.

“No way, man.” said Valeros. “I’m not fighting that thing again. She’ll kill me!”

“Fine, let’s trade places.” Merisiel relocated to the farmhouse and Valeros took over the forest. Suddenly, a bandit henchman (or something) leapt out at Merisiel from the tall grass of a pasture. She threw a dagger through his heart and then, as a result, managed to close the location. Yet as she was boarding up the doors and windows of the farmhouse she spotted Vhiski himself hiding in a corner. She decided not to let her presence be known just yet and communicated with Valeros (who was in the woods) via carrier pigeon.

“Enemy spotted. He’s strong. I think I can take him, but you may be better with your superhuman strength.” she wrote.

They did some quick math and confirmed that Valeros, with his mighty strength and stuff, did indeed have a better chance of defeating the villain. Merisiel could provide some healing assistance if required. They formulated their cunning plan. It went like this:

Merisiel and Valeros quietly switched locations again - Merisiel retreating to the woods and Valeros sneaking into the farmhouse. Valeros pounced out at Jubrayl Vhiski, charging at him with all his might. A very fast carrier pigeon sent word to Merisiel that the battle was beginning. She, in the meantime, was trying to figure out how to temporarily close the woods to ensure Jubrayl couldn’t escape there when Valeros defeated them.

“But oh no!” she exclaimed. “It requires wisdom! We are toast. Even if Valeros manages to defeat Jubrayl now, he’ll just escape into the woods and we’ll have to wade through all these hostile creatures. We’ll be dead before we get to him!”

They should probably have taken the time to figure this out before putting their plan in action, but as we’ve stablished - not much wisdom on either end.

Pathfinder: wisdom roll Merisiel had one shot at solving a seemingly impossible puzzle. All she could do was guess. Out of six choices, only one was correct. There was zero room for error. Sweating profusely, she guessed…”Six!

The monsters of the forest cried out in horror and retreated to their dens. The forest closed in on itself. Success!

In the meantime, Valeros was fighting an epic battle with a gang leader who was beating him with a club.

“You can do it, Valeros!”

And he did! With a final good kick to the head, Jubrayl was knocked unconscious (permanently). Valeros looked to the sky at just the right angle for sunlight to shine on his chiseled features.

And so, Sandpoint was freed from the clutches of an evil villain.

“We did it!” Merisiel punched the air victoriously, nearly stabbing Valeros in the face with her dagger. “Let’s do that again!”

They started dividing their loot, preparing for their next (big) adventure.

It was decided that Merisiel should probably hand over the axe to Valeros, who was able to use it without injuring himself. Valeros then passed out, clearly requiring another nap (seriously, he’s snoring right now).

Wolfling Runs

Wolfling run: “a method of playing the Creatures games in which the players do not intervene in the lives of their norns.” Creatures wiki.

Here is one of the original detailed explanations - Wolfling Run FAQ

I’ve mentioned implementing a way to do wolfling runs in Gastropoda a few times, but then realized that it’s not exactly a commonly known term (at least for people who’ve never played Creatures). I only recently learned that the term is actually based on David Brin’s Uplift novels.

Most of humanity believes itself to be a wolfling species that emerged into sapiency solely through natural evolution, without genetic manipulation of a patron species. Uplift Universe wiki

There are different kinds of wolfling runs, but basically when I played Creatures 3 I mostly ran relaxed wolfling runs. I’d set up the world, then leave my norns for a few hours to breed on their own. It was always really interesting to see which ‘lines’ survived. I even made up stories for them, documenting each norn’s life and breeding patterns in a notebook. At the end of the wolfling run I’d crown an alpha male and female - those who spread their genes the most during the run.

Anyway, I want to set up something similar for the snails. I haven’t yet decided if this will be the default state for the breeding jars…I’ll either just have a Wolfling Run checkbox (or item) that makes the jar disregard capacity and let the snails breed wild. At the moment, breeding events are only triggered when you move a snail into the breeding jar. In reality, I suppose snails would periodically try to mate whenever they’re in the breeding jar. I’d have to get the balance right, make it realistic…they can’t just be breeding like rabbits. The other alternative is requiring the user to get a really high capacity breeding jar. These would be very expensive in terms of the virtual currency, but would be perfect for wolfling runs.

I might start on this now and then move on to race actions.

On Snail Eggs

Even though I’ve had a functional breeding system in Gastropoda for a while, it still isn’t actually done. Most common snails do not give birth to live young…and definitely not to one live young. Initially it was good to just have the snails pop out one live baby snail so that I could properly observe and test the genetics and other attributes generated for the newborn. Now, however, with that being well on track, it is time to implement…egg laying.

The requirements were this:

  • Quantity of eggs is based in part on the snail’s fertility attribute. Environmental and health factors will eventually come into effect.
  • The snail now has to be pregnant for some time before actually laying the eggs. Eggs have to stay in the jar for some time before they hatch into baby snails.
  • The baby snails are pregenerated at the time of the mating, not on egg hatching. This is because eventually environmental factors and the parents’ health may have some effect here as well. I can easily modify additional attributes before hatching if needed, but the main attributes of the newborn snails must be set based on the state the parents were in when they actually mated.
  • Snails can’t be born at their full size - they have to be born tiny and grow based on a maturity rate attribute.

At first I considered having a separate table for eggs, but then I decided not to complicate things. I added an eggLaid attribute to the snail table. By default eggLaid is set to false, so at the time of conception the snail-to-be exists but it doesn’t appear as an egg. The birthDate is NULL at this time. In the jar we can see the eggs displayed on top of the pregnant snail’s shell like so:

Pregnant snail

(The number of circles on the shell doesn’t represent how many eggs will actually be laid. In the future I’ll make this much more subtle, the quantity will be indistinguishable).

A LayEggs event is created in the db. Currently it’s always scheduled for exactly an hour after mating. When this event runs eggLaid is set to true in the snails table for those snails and they now appear as eggs (you can see an example of one of the eggs in the jar above). The color of the egg represents (to an extent) the shell color of the newborn, though without the pattern overlay it’s impossible to tell what the final snail will look like at this time. For me that’s pretty exciting, because I can come back in a few hours to start seeing what kind of shell pattern and eye color my baby snail is developing.

When the eggs are laid, a HatchEggs event is created. Eggs will hatch roughly 24 hours after they’re laid, but this will eventually have a tiny degree of randomization as well as be affected by factors like temperature and subtrate within the jar.

When the eggs are hatched, the snail is so tiny that you can barely see it. From there every hour the scale of the snail is increased based on the maturity rate attribute. Eventually the snail grows to its full size!

Up next I have to:

  • Make sure that snails under 3 days of age do not count toward the jar capacity quota
  • Make sure that snails can’t be picked for breeding until they’re fully mature
  • Implement aggression - baby snails are vulnerable and can be eaten by other snails