Liza Shulyayeva

SnailLife Messaging System

“What’s SnailLife”, you say? Well I’m glad you asked! My snail simulation has gone through a couple of names…and even though I was never 100% happy with Gastropoda it was the best I could come up with - a name that was unique and didn’t allow the project to sound too “gamey” (because it’s not a game). All of the cooler names I could come up with weren’t suitable for various reasons (like domain name availability and such).

But recently I found out about the .life TLD! And I decided that nowadays we have such a varied domain name landscape that .com isn’t as important as it used to be, and definitely not for an obscure hobby project. So Gastropoda is now SnailLife! On to the messaging:

Messaging

I noticed when working on the simulation that it was tough to figure out who died and why when snails disappeared from a jar.

(Sidenote…I just realized…in real life a snail wouldn’t just disappear from a jar if it died. It would sit there and start decomposing until someone put it away. Maybe this should be the case with SnailLife, too).

Anyway, I wanted something to notify me immediately when something important happens, like a snail is born or dies. So I made a rudimentary messaging system to receive notifications from the simulation, which should be able to be pretty easily expanded into a user-to-user messaging system.

First I made a user_messages table with the following columns:

  • messageID
  • recipientID
  • senderID
  • subject
  • content
  • isRead
  • created_at
  • updated_at

Then I made a UserMessage model that looks like this for now:

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class UserMessage extends Model {

    public static $rules = array(
        'recipientID'     => 'integer',
        'senderID'   => 'integer',
        'subject'    => 'alpha_num_spaces',
        'content'    => 'alpha_num_spaces',
        'isRead'     => 'boolean'
    );

    protected $primaryKey = 'messageID';

    protected $fillable = ['recipientID', 'senderID', 'subject', 'content', 'isRead', 'created_at', 'updated_at'];

    public function recipient() {
        $this->hasOne('App\User', 'userID', 'recipientID');
    }

    public function sender() {
        $this->hasOne('App\User', 'userID', 'senderID');
    }

    public function getSenderUserNameAttribute() {
        $username = 'SnailLife';
        if (isset($this->sender)) {
            $username = $this->sender->username;
        }
        return $username;
    }

    public function updateMessage($propertiesToUpdate) {
        $this->update($propertiesToUpdate);
        return true;
    }
}

When a snail is killed or born we create a new message for the user. For example:

if ($this->isEgg) {
    $notification = [
        'recipientID' => $this->ownerID,
        'subject'     => 'An egg has died!',
        'content'     => 'Egg ID ' . $this->snailID . ' has died. Cause of death: ' . $cod
    ];
}
else {
    $notification = [
        'recipientID' => $this->ownerID,
        'subject'     => 'A snail (' . $this->snailID . ') has died!',
        'content'     => 'SnailID ID ' . $this->snailID . ' has died. Cause of death: ' . $cod
    ];
}
$notification = new UserMessage($notification);
$notification->save();

(An egg is really just an instance of a snail, just one without a birthDate, so when an egg or snail dies it’s handled in the same model).

Then there’s the view. When logged in the user gets a notification of unread messages in the header:

<p>
    @if (count(Auth::user()->unreadMessages) > 0)
        <a href="/account/messages"><img src="{!! URL::asset('assets/img/graphics/icons/envelope.png') !!}" alt="You have unread messages"></a> - You've got mail!
    @endif
</p>

Oh, we get unread messages in the User model using an Eloquent hasMany relationship:

public function unreadMessages() {
    return $this->hasMany('App\UserMessage', 'recipientID', 'userID')->where('isRead', '=', 0);
}

Once they click through they get taken to their message page (ignore the double-death messages. That’s being fixed right now…):

User messages

There they can click to view the individual message, delete messages, mark them as read, etc.

Pretty simple and gets the job done for now.

Update on Rigel and Response From DjurAkuten

Context: The Month from Hell.

DjurAkuten

After numerous attempts David finally got through to DjurAkuten, the clinic that did Rigel’s neutering surgery and decided to put a catheter in him because they thought his bladder looked big - the one he left with a urinary infection and an inability to pee.

He spoke to them about what had happened. They requested that we email them all of Rigel’s journals, which we did the same day. A week or two later a letter came in the mail (in Swedish) where they claimed that they had zero responsibility for what happened to Rigel because his bladder was already flaccid when he got there. I’m not sure if they even read their own journals on him. DjurAkuten themselves wrote in their records that they found no blockage and no infection in Rigel’s urine and that his bladder contracted normally while being drained during castration - it was certainly not flaccid. They simply thought his bladder was large and decided to poke around with a catheter during what was meant to be a routine surgery.

It’s disappointing because either they are contradicting their own paperwork without even reading it or they are being intentionally misleading. I might upload all of his journals here later for anyone who’s curious and is willing to either translate or reads Swedish.

Here was my email to them in response, sent a few weeks ago, to which we have had no reply even after calling (I have censored the names of the other clinics involved here):

Attn: Ms Susanne Åhman regarding Rigel‏

Dear Ms Åhman,

I am contacting you through email because it is faster than sending physical mails back and forth.

Please let me know if I misunderstood your mail to us about Rigel, but it seems you are saying DjurAkuten takes zero responsibility for anything that’s happened because our kitten came in with a large bladder? The part in your mail about him having a flaccid bladder is not correct. Your own journals have said that his bladder contracted normally after you decided to stick a catheter in him: “efter tömning drar ub ihop sig vad som ser ut som normalt.” It was certainly not flaccid as your own records show.

In fact, the first time your own journals mention a flaccid bladder was when we brought him back to you the 2nd time when he was unable to pee after your treatment.

We are certainly not saying that the problems were as a result of his castration as your mail seems to imply - we are saying his problems are as a result of your introducing a urinary infection while sticking a catheter in him during what was supposed to be simply a castration surgery. There was no good reason to do this.

An Internal Medicine specialist has examined him and communicated that cat organs, just like human organs, can vary in size and shape. We have been told that just because a bladder is large does not mean it has a problem. The fact that he developed an infection after you decided to put a catheter in him is also indicative of an issue being introduced at your clinic. Our kitten was perfectly happy and healthy before his procedure at your clinic resulted in a urinary infection. I can assure you he did not come in sick with an infected, distended bladder. I know this not just by our own observation of him and his very normal urination frequency before your treatment, but by looking at your own journal notes - you decided to put a catheter in him when you noticed his bladder was large and tested for infection. You found no infection. You have also said that his bladder contracted normally after being drained at that time. Your own vet spoke to me in person after the neutering surgery and said that his large bladder can be totally normal for him and not cause any further problems, especially since he’s been peeing normally all along. We came back to you when our kitten could not pee despite straining to after your surgery and catheterization. You stuck ANOTHER catheter in him to drain him AGAIN and said that he now suddenly has an infection. This is what started this entire chain of events - us bringing a healthy, happy kitten to DjurAkuten for a routine surgery and leaving with a kitten who was unable to pee and had to go through a subsequent bladder biopsy surgery, perineal urethrostomy, and countless catheters, blood tests, and needles being stuck into his bladder. He was sent home to die and we still have no idea how he managed to survive after all this. He is still on heavy medication, special food, and constant watch (we still monitor his urine clumps in a box daily to make sure he is urinating enough after this entire ordeal). We have just begun to get over everything he’s been through and everything we’ve gone through to keep him alive, but now seeing you claim that he came to you already sick despite all evidence to the contrary is even more upsetting.

So - am I understanding correctly in that you claim DjurAkuten did not have anything to do with the initial problem - him being unable to pee after being catheterized at your clinic - even though you tested (and cleared) him for infection, despite the fact that other veterinarians have indicated that he was fine before he saw you and got sick after you decided to stick a catheter in him, and that the treatment he received at your clinic and subsequent infection are more than likely related? You will note that in the journals [other clinic] concludes that his original infection was iatrogenic in nature, ie, introduced in the catheterization process. Please confirm if this is the case, as I want to make sure I have correct information about your standpoint on this before we decide whether to pursue a formal complaint and proceeding to warn our friends, breeders, and the public about your clinic.

Kind regards,

Liza Shulyayeva

We are still hoping that they will reply and clarify what they meant by their mail. It’s possible that it was some sort of misunderstanding, and that DjurAkuten didn’t mean to suggest that Rigel was sick all along despite all evidence indicating otherwise. And if they were really trying to convince us that we brought them a sick kitten despite their own records indicating otherwise I will be extremely disappointed as this would indicate their miscomprehension of their own and other hospitals’ records at best and a blatant lie at worst.

Rigel

Rigel has been suffering from yet another urinary infection after this whole debacle. Perineal Urethrostomy increases the likelihood of infection, and I guess he got unlucky. He is on antibiotics and painkillers/anti-inflammatories. He seems to have gotten worse over the last day (peeing blood again and straining, and starting to pee outside the box) so I rushed another urine sample to the hospital at 4-5am this morning and managed to get an appointment with a vet this afternoon. By the time we had our appointment they had already gotten the results of the sample and said that there was no more bacteria! What was an infection is now a case of cystitis, so our next steps are to:

  • Finish course of antibiotics
  • Appetite booster every other day and special appetite-boosting food
  • Continue with Cystease supplement for bladder lining
  • Start giving Zylkene supplement for stress
  • Continue giving Metacam for 5 days for pain relief and as an anti-inflammatory.
  • Minimize noise and stress as much as possible (coincidentally this seems to have started when David’s kids came over to stay for two weeks a couple of days ago, so noise and excitement maybe triggered something, even though he really likes them).

They also took a blood test and checked his kidney, liver, and other values. Everything seems to be normal, which is a load off my chest.

It’s crazy that one bad incident at a bad vet has spun us into what seems to be an ongoing cycle of vet visits, tests, surgeries, hospital stays, two closets of cat medicine, and endless vet/medicine bills. It’s still amazing to me that he has survived all of these hospital stays and other procedures with his affectionate personality and happiness intact. The difference in his and Kaytu’s approach to life is obvious, though. Even though they’re best friends it’s obvious that Kaytu, with her seemingly carefree personality, hasn’t been through what Rigel has been through - you can see it in their eyes and in how they approach strangers and the outside world. He’s a very strong kitten and we will keep fighting for him and his quality of life no matter what it takes.

Snaily Updates

I was going to try to remember all the things I’ve done in the last month on snails, but this seems impossible. So here’s a lazy list of git commits within this time:

* 5 seconds ago, User messaging and cause of death [deploy:development]
* 4 days ago, Some race view fixes, test out increasing AMR for wild snails since too little energy is being burnt during races. [deploy:development]
* 4 days ago, Cancel races that have been unfinished for 24 hours, freeing up others to rent the race jar. [deploy:development]
* 5 days ago, Remove old unneeded Minion and Latchet stuff [deploy:development]
* 5 days ago, Remove unneeded logs, remove old breeding stuff that is no longer required with the brain [deploy:development]
* 5 days ago, Fixes and cleanup, new racing jar in seeder [deploy:development]
* 5 days ago, Remove start countdown from finished races, fix race result view [deploy:development]
* 5 days ago, Fix race finish [deploy:development]
* 5 days ago, Bug fixing in racing and memory saving [deploy:development]
* 6 days ago, Admin option to return foreign snails to owners, get parent jar [deploy:development]
* 6 days ago, Racing refactoring; stop destroying jars since we'll reuse them [deploy:development]
* 7 days ago, More flexibility for historical event logging; correctly represent snails trying to mate with items [deploy:development[
* 8 days ago, Substrate item creation fix [deploy:development]
* 8 days ago, Gather snails to middle of jar, more exact movement, seed race jars for Mr Casinir [deploy:development]
* 2 weeks ago, Ability to humanely euthanise eggs in jar [deploy:development]
* 2 weeks ago, Some safeguards for user entering snail in race before it was returned from previous race [deploy:development]
* 2 weeks ago, Replace finished and started columns with finishDate, startDate. Auto refresh jar page when race has started. [deploy:development]
* 3 weeks ago, Fix move check for jar view [deploy:development]
* 3 weeks ago, use View [deploy:development]
* 3 weeks ago, Fix swallowing, isolate mood update [deploy:development]
* 3 weeks ago, Resident register [deploy:development]
* 3 weeks ago, Draw gravestone under snail pattern [deploy:development]
* 3 weeks ago, Add rainbow bridge page [deploy:development]
* 3 weeks ago, Display estimated race start time [deploy:development]
* 3 weeks ago, Fix fertility spray [deploy:development]
* 3 weeks ago, Fix bank link [deploy:development]
* 3 weeks ago, Only list items user owns in closet [deploy:development]
* 3 weeks ago, Fix for jar installation [deploy:development]
* 3 weeks ago, Add bank hint to tutorial [deploy:development]
* 3 weeks ago, Switch to Single log mode [deploy:development]
* 4 weeks ago, CustomValidator [deploy:development]
* 4 weeks ago, Stop comparing an item to itself... [deploy:development]
* 4 weeks ago, User shouldn't be able to mix a substrate item with itself [deploy:development]
* 4 weeks ago, Toggle disabling item action form elements on action select [deploy:development]
* 4 weeks ago, Some item stuff [deploy:development]

I also decided to try live streaming Gastropoda development. I’ve only done a couple of sessions so far but it does help to keep me focused. Surprisingly enough a few people actually watch and ask questions. I didn’t really think anyone would be that interested in watching someone code some weirdo snail app. It’s nice because a lot of the “Have you thought about doing this” or “What about this” suggestions people have made are things I have thought of and/or already have implemented!

Items

After finishing yesterday’s post about generating images for user-combined substrate items I realized that I never really provided an explanation about how items work in general (or how they work for now, anyway).

We have a few different item-associated models in Gastropoda. They are (model followed by associated table in parinetheses)

  • ItemType (item_types)
  • ItemTemplate (item_templates)
  • ItemUserTemplate (item_user_templates)
  • ItemNutrition (item_nutrition)
  • ItemUserNutrition (item_user_nutrition)
  • Item (items)

Item types are very generic. Right now I have the following item types:

  • consumable
  • decorative
  • substrate
  • jar
  • breeding
  • terrain
  • training
  • misc

ItemTemplates are pregenerated item…templates that can exist in the world. Note that these are not the existing items in the world, they are just things that could be instantiated into in-world items. Each item template is associated with an item type. Right now they are (template name, item type):

  • Lettuce Leaf (consumable)
  • Medium Sweet Potato (consumable)
  • Spark (decorative)
  • Basic Small Jar (jar)
  • Enthusiast Small Jar (jar)
  • 7-Day Fertility Spray (breeding)
  • Grooved Glass 2mm (terrain)
  • Attractor (training)
  • Garden Dirt (substrate)
  • 1kg Pure Fine Sand (substrate)
  • 1kg Dry Oak Leaves (substrate)

ItemTemplates can have an associated nutrition template. All consumables and substrate items have their own entries in the item_nutrition table.

Items are the actual instances of these item templates existing in the world. They are usually created when stores are restocked or when users mix their own substrate. An item entry consists of basic information about the instance, like:

  • itemID
  • templateID
  • ownerID
  • jarID
  • bitesTaken
  • posX
  • posY
  • isUserTemplate

Which brings me to user templates. Any mixing of substrate creates a new item template, or rather an ItemUserTemplate, and saves it in the item_user_templates table. Substrate also gets its own nutrition entry, in item_user_nutrition. This allows us to easily distinguish user generated items from basic items in the simulation and treat them differently. Then on an instance of an item, if isUserTemplate is true, we know to look for associated templates in the user tables and not the regular tables.

That’s pretty much it. The most annoying part about this setup is that there are so many inter-table dependencies. An item without a template entry is a broken item. A consumable without a template that has an associated nutrition record is broken. Etc…

Images for Combined Substrate

The Laravel 5 migration is complete and I’m back to substrate mixing and brain bug fixing!

Actually, I think now that basic mixing is done I’m going to do a few weeks of just bug fixing. The substrate has no effect on snails’ attributes or behaviour yet, but it will. For now you can just mix different substrate items together into new substrate items.

I’m still not sure what the best way to create images for the custom-mixed substrate might be. The possibilities are pretty much endless - you can mix any substrate-type item with any other substrate-type item.

So what happens is - the user goes to their closet. They pick two substrate items to mix together. Then this whole mess happens:

 protected function mixSubstrate() {
    $itemID1 = Input::get('itemID1');
    $itemID2 = Input::get('itemID2');

    $item1 = $this->findItem($itemID1);
    $item2 = $this->findItem($itemID2);

    $item1WeightG = $item1->template->weightG;
    $item2WeightG = $item2->template->weightG;

    $resultingItemName = Input::get('itemname');

    $item1Attributes = $item1->template->nutrition->getAttributes();
    $item2Attributes = $item2->template->nutrition->getAttributes();

    $imageTitle = preg_replace('/\s+/', '', $resultingItemName) . '.png';
    $fileToSaveTo = '/assets/img/items/terrain/custom/' . $imageTitle;

    $resultingImage = Utility::MergeTwoImages($item1->fullImagePath, $item2->fullImagePath, $fileToSaveTo);

    // Create resulting item template
    $resultingItemTemplate = new ItemUserTemplate();
    $resultingItemTemplate->description = 'Custom item created by combining ' . $item1->template->name . ' and ' . $item2->template->name;
    $resultingItemTemplate->weightG = $item1WeightG + $item2WeightG;
    $resultingItemTemplate->name = $resultingItemName;
    $resultingItemTemplate->typeID = $item1->template->typeID;
    $resultingItemTemplate->ownerID = Auth::user()->userID;
    $resultingItemTemplate->imageSubdir = 'terrain/custom/';
    $resultingItemTemplate->imageName = $imageTitle;
    $resultingItemTemplate->save();

    // Create resulting item nutrition template
    $resultingItemNutrition = new ItemUserNutrition();
    $resultingItemNutrition->templateID = $resultingItemTemplate->templateID;
    foreach ($item1Attributes as $key => $value) {
        if ($key !== 'templateID' && $key !== 'updated_at' && $key !== 'created_at') {
            $resultingItemNutrition->$key = $item1Attributes[$key] + $item2Attributes[$key];
        }
    }
    $resultingItemNutrition->save();

    // Create item of new template
    $resultingItemProperties = [
        'templateID' => $resultingItemTemplate->templateID,
        'ownerID' => Auth::user()->userID,
        'isUserTemplate' => 1
    ];
    $resultingItem = $this->createNewItem($resultingItemProperties);

    // Delete original items
    $item1->deleteItem();
    $item2->deleteItem();

    return Redirect::back()->with('resultingItem', $resultingItem);
}

To actually create the image for the new item I added a new utility function:

public static function MergeTwoImages($imgPath1, $imgPath2, $fileToSaveTo = null) {
    $x = 50;
    $y = 50;
    $root = $_SERVER['DOCUMENT_ROOT'];

    $finalImage = imagecreatetruecolor($x, $y);
    imagesavealpha($finalImage, true);

    $sortedImages = [$imgPath1, $imgPath2];
    sort($sortedImages);

    $image1Layer = imagecreatefrompng($root . $sortedImages[0]);
    $image2Layer = imagecreatefrompng($root . $sortedImages[1]);

    $opacity = 50;

    ImageCopyMerge($finalImage, $image1Layer, 0, 0, 0, 0, 50, 50, $opacity);
    ImageCopyMerge($finalImage, $image2Layer, 0, 0, 0, 0, 50, 50, $opacity);

    header('Content-Type: image/png');
    if ($fileToSaveTo) {
        $fileToSaveTo = $root . $fileToSaveTo;
        imagepng($finalImage, $fileToSaveTo);
    }
    return $finalImage;
}

The results look like this (the below is experimenting with mixing 1kg oak leaves with 1kg sand):

Mixed substrate

It’ll do for now. After some bug fixing I’ll go back to substrate and start having it actually have an effect on the snails.

Migrating to Laravel 5

A few days ago I decided that there’s no use putting it off any longer - it’s time to upgrade to Laravel 5. I’ve been sitting on 4.2 for months and 5 is a major revision to…well…everything.

I knew this would be a large setback in terms of feature work. The migration would break things, things that would make me wish I’d never done it at all. In addition the plugins I’d been using for 4.2 would no longer be compatible with 5 (in fact, I don’t think any of them are…)

I was using:

  • Confide for user authentication
  • Dispatcher for scheduled commands
  • Latchet for a Laravel-specific implementation of Ratchet.

The first two I just got rid of completely, using Laravel 5’s authentication features and new command scheduling feature that does exactly what Dispatcher used to for me.

The last one I haven’t even started on yet. It’s going to be the toughest.

The most “breaking” update in Laravel 5 has been namespaces. I’ve been sitting here adding namespace declaration to all of my files. It’s time consuming, but better in the long run and actually pretty relaxing. If this is the biggest problem I’ll run into during this migration I’ll be happy (Latchet aside, of course).

Anyway, within the next couple of days I hope to have the migration complete and functional. Then it’s back to substrate and brains!

Thinking Through Substrate

After checking in the basic substrate item implementation yesterday I got to thinking - never a good sign.

The original idea was to have some pre-set types of substrate - garden dirt, rock, potting soil, pellets, whatever. But how would these items affect the snails and the jar? Would I need to invent some sort of set of attribute templates? Like “Oh well this garden dirt item is going to decrease jar temperature by 1C.” and “Oh well some snails somehow don’t like gardend dirt and prefer other substrates”. But why? Why would a snail prefer one template to another and how would this be decided?

Considering I’m already going for a lot of detail with consumables (making sure consumable items have macro and vitamin attributes based on their real life counterparts), I should maybe do the same thing for substrate. For example, garden dirt isn’t just garden dirt…it can contain varying amounts of clay, humus, sand, rock, etc. An item called “Garden Dirt” would really be made up of multiple base elements in varying proportions. And that led me to thinking - while a template called “Garden Dirt” should exist and be available for pre-mixed purchase from the Habitat store, users should also be able to mix these elements themselves. As a user I want to buy my own sand, clay, rocks, whatever, and experiment with proportions of each to create substrates of different moisture, porosity, pH level, etc.

I don’t know exactly how this is going to work yet - in fact from this point on I’m kind of making this up as I type. But here’s a quick overview of how consumable items already work.

The database has the following item-related tables (with the specified columns):

  • item_types
    • typeID
    • name
  • item_templates
    • templateID
    • typeID
    • luminance
    • temperature
    • roughness
    • description
    • enduranceMod
    • speedMod
    • aggressionMod
    • modDurationMins
    • weightG
    • bites
    • baseDaysToRot
    • imageName
    • imageSubdir
    • rarity
    • basePrice
    • priorityLayer
    • pingsJar
    • isApplied
  • item_nutrition
    • templateID
    • carbohydrates
    • sugar
    • fat
    • protein
    • vitA
    • vitC
    • vitD
    • vitB6
    • vitB12
    • calcium
    • iron
    • magnesium
  • items
    • itemID
    • templateID
    • ownerID
    • jarID
    • deleted
    • bitesTaken
    • posX
    • posY

I suspect that I will need another table on the same “level” as item_nutrition. Something like item_terrain_elements…or something. It would contain the following columns (or something like this):

  • item_terrain_elements
    • templateID
    • pH
    • porosity
    • carbon (primary nutrient)
    • hydrogen (primary nutrient)
    • oxygen (primary nutrient)
    • nitrogen (primary macronutrient)
    • phosphorus (primary macronutrient)
    • potassium (primary macronutrient)
    • calcium (primary macronutrient)
    • magnesium (primary macronutrient)
    • sulfur (primary macronutrient)
    • iron (primary micronutrient)
    • manganese (primary micronutrient)
    • boron (primary micronutrient)
    • copper (primary micronutrient)
    • zinc (primary micronutrient)
    • molybdenum (primary micronutrient)
    • chloride (primary micronutrient)
    • nickel (primary micronutrient)
    • iodine (other)
    • fluorine (other)
    • selenium (other)
    • cobalt (other)
    • arsenic (other)
    • lithium (other)
    • chromium (other)
    • silicon (other)
    • tin (other)
    • vanadium (other)

The primary nutrients would affect plant growth and the “other” nutrients could affect the animals (ie snails) within the jar.

And looking at the above just now, I don’t see a reason not to just add the above nutrients to the item_nutrition table. Some of the columns (eg magnesium) would already be duplicates anyway…I could maybe just add the above columns to the nutrition table and set anything irrelevant to ‘0’…would there be something wrong with this? I don’t know, I have to think.

Jar Substrate Is a Go

It’s finally in. Well, the beginning stages anyway. Jar substrate. Anyone who knows anything about snails knows that they can’t live in a glass box without some sort of substrate to hide in, get moisture from, and burrow under. So now you can apply one type of substrate (so far) to a jar. Eventually there will be different kinds of substrate, and maybe even substrate you can mix together from other types of substrate. Currently the substrate has no effect on the snails or the environment outside of being present and visually represented, but eventually it can impact everything from snail nutrients to jar temperature and what kinds of plants you can grow inside the jar. Here’s how it works.

I added a new column to the item_templates table:isApplied. If an item template has this set to true it means that you apply it to a jar instead of placing it into the jar as a moveable item (like a potato, for example). Once you place substrate in you cannot drag it around, you can only remove it.

The substrate item has a terrain item type in the item_types table. There are different actions available from the user’s closet based on item type and sometimes template:

    protected function getAvailableActionsAttribute() {
        $possibleActions = [];
        switch ($this->template->type->name) {
            case 'consumable':
                array_push($possibleActions, array('text' => 'Put in Jar', 'onclick' => "return showPutInJarForm($this->itemID)"));
                break;
            case 'decorative':
                array_push($possibleActions, array('text' => 'Put in Jar', 'onclick' => "return showPutInJarForm($this->itemID)"));
                break;
            case 'substrate':
                array_push($possibleActions, array('text' => 'Put in Jar', 'onclick' => "return showPutInJarForm($this->itemID)"));
                break;
            case 'jar':
                array_push($possibleActions, array('text' => 'Install Jar', 'onclick' => "return showInstallJarForm($this->itemID)"));
                break;
            case 'breeding':
                if(strpos($this->template->name,'Fertility Spray') !== false) {
                    array_push($possibleActions, array('text' => 'Spray', 'onclick' => "return showPutInJarForm($this->itemID)"));
                };
                break;
            case 'training':
                array_push($possibleActions, array('text' => 'Put in Jar', 'onclick' => "return showPutInJarForm($this->itemID)"));
                break;
            case 'terrain':
                array_push($possibleActions, array('text' => 'Apply to Jar', 'onclick' => "return showPutInJarForm($this->itemID)"));
                break;
        }
        return $possibleActions;
    }

So if the item type is ‘terrain’ the action text that appears is “Apply to Jar” vs “Put in Jar”. It could call a different function when clicked as well, but in this case we handle the application step in the same function as the putting-in step so we show the same form.

So a user goes to their closet, scrolls to the Garden Dirt item, clicks “Apply to Jar”, and selects which jar to apply the item to.

Then we update the item as we usually would by placing it inside the jar, except we only assign the item a position if !$item->isApplied. At the end we run any special actions the item may have (which in this case it does) via item->runSpecialActions():

case 'terrain':
    $jarController = new JarController();
    $jar = $jarController->findJar($this->jarID);
    $jar->terrainItemID = $this->itemID;
    $propertiesToUpdate = [
        'terrainItemID' => $this->itemID
    ];
    $jar->updateJar($propertiesToUpdate);
    break;

So the terrain gets applied. The item is not deleted until the substrate is thrown away. And if a jar has a terrain applied we draw the terrain tiles in the background:

Jar with substrate

Of course the next step is to have the substrate actually have some sort of effect. Snails housed in a jar without substrate should be affected by the lack of proper environment. Unfortunately right now I do not yet have snail health implemented. That is, snails can die of lack of energy via starvation, but they also need to be able to get ill from environmental and genetic factors and then die that way, too. The terrain will be just one of many things that should affect a snail’s overall health and performance.

The Month From Hell

Update on Rigel and response from DjurAkuten

Update: When I first wrote this post I kept all clinic names anonymous. I am now un-anonymising one of the clinics (DjurAkuten), where this whole chain of events was triggered in the first place and which is now trying to claim that Rigel was already sick/had a flaccid bladder when we brought him in for a routine surgery as a healthy kitten despite their own records stating otherwise.

Note: Veterinarian clinic/hospital names in this post have been changed. If you know the vets in Stockholm you may be able to easily get an idea of which places I’m talking about, and if you want some clarification you can message me in private. I wasn’t sure if it was right to include real names here.

This is Rigel on his trip home to us from his breeders in Norrköping. He was 3 months old and the nicest of cats. He slept quietly through the long train ride to Stockholm. When we got home he walked out of his carrier, sniffed around, and claimed the house - and us - as his own.

Here I am channeling Overly Attached Girlfriend on his first day home:

Rigel was the most amazing kitten. He’d follow us around everywhere, do flop-downs and chirp at us; he talked to us, developed his own quirky habits and personality. He was one of the best things that’s ever happened to me.

On February 12, at a bit over 6 months of age, Rigel had his neutering surgery. He was born with a condition called “cryptorchidism”, which meant that his testicles hadn’t descended. This didn’t cause him any health problems, but it did mean that the neutering surgery would be a little more involved - it would more closely resemble spaying a female because the surgeon would have to go into the abdomen to retrieve the testicles. Nevertheless, it’s meant to be very safe and routine. We took him to a clinic a little north of T-Centralen that we heard great things about (even though we live South of the city), DjurAkuten. I was worried as you would be with any surgery, but this is done all the time and we heard good things about them.

DjurAkuten actually did an ultrasound before the surgery to find Rigel’s testicles in his abdomen (instead of having to dig around in there looking for them during the surgery itself). I hung around the area for a few hours during the procedure, waiting for the call that he was ready to go home.

When I finally came to pick him up the veterinarian wanted to talk to me. She said the surgery went well, everything was fine, but they did notice one thing during the operation - he had what they thought was a very big bladder that was also more elongated than a “normal” bladder would be. They thought there may be some blockage or infection so they passed in a catheter to check for both. They drained the urine with a catheter, found no blockage, and no infection. They said the bladder contracted nicely after being drained. So he was good to go! I thanked her and brought Rigel home, excited to have him start his post-op recovery.

Unfortunately on February 14th we noticed that Rigel wasn’t peeing. He kept straining to go to the litter box, but nothing was coming out. We read that after an operation the cat’s urination and pooping habits might be a bit wonky, but this was worrying - he just couldn’t seem to pee.

We took him back to DjurAkuten, where they I think passed in another catheter to drain the urine and again tested for infection. Unlike the test during the surgery, they now found bacteria. They sent him home with antibiotics.

The next day he still did not pee. We rushed him back to DjurAkuten. They said that at this point they should put in a catheter and keep him overnight. However, DjurAkuten do not have staff on-site 24/7 and said they would understand if we wanted to go to a larger hospital who did. They recommended Baker, a big hospital about 5km from our house. We took a taxi there instead.

During the consultation Rigel was trying to pee and got a few drops of bloody urine out. At this point we were starting to put two and two together and the veterinarian diplomatically confirmed - judging by the fact that Rigel was totally fine and had no problems peeing before the surgery, that the first catheter passed in during the surgery found no bacteria and two days later there was suddenly an infection, it seems that DjurAkuten introduced a urinary infection while testing for a urinary infection. We were devastated - here we had a perfectly healthy kitten who went in for a routine surgery, had a catheter stuck in him (unrelated to the actual surgery) because they thought his bladder looked big, and was now in this mess. Thank god…or whoever…we had insurance.

Rigel stayed at Baker for I think two nights with a catheter (though I’d have to check the journal to know for sure). Finally they called us and said he peed on his own and that he can come home! We were ecstatic - our kitten was ok again!

So we took him home. Except he still didn’t pee on his own. And this time he wasn’t even trying. At first I thought maybe that’s a good sign…maybe his bladder just hadn’t filled enough? If he didn’t need to pee that’s good, right?! When he hadn’t peed in 24 hours we took him back again. They drained 400ml of urine. Did they send him home with an already filling bladder?! And now there was talk of atony and distension. This is where “distended bladder” starts showing up in the journal, and nerve damage. It wasn’t good that he didn’t feel the need to pee after all - it was a sign that his bladder was now damaged to the point of him not being able to feel the need to go. The infection may be gone, but now we are on to a whole new problem.

They kept him again. We visited him every day. They put him on new meds and put in a catheter.

A few days later they called us saying he ripped out his catheter, but we should take him home because the new meds seem promising. We take him home. At this point our nerves are shot and we have no idea what’s going on. We just want him to get better.

Rigel has been to the hospital so much now that we have a routine. I bring him in. They take him to an individual waiting room after the main waiting room. I take off my jacket and spread it out on a chair for him. He curls up there and has a nap.

He tries to pee and can’t (but at least he tries now). We rush him back. This is a nightmare. I notice the penis looks a bit swollen and ask someone if it’s possible that there is now a blockage or injury or scarring or SOMETHING there after he ripped out his catheter. They say it looks a little swollen but that’s normal after the catheters. They suspect the problem is the bladder, not the urethra - but then why is he trying to pee? Clearly he can feel his bladder still! We are very confused, but the vets are saying he’s been checked for blockage several times and there has been none. Ok, fine..but he hasn’t been checked since he last ripped out his catheter, right? Baker has also been hoping that letting his bladder get full will encourage him to pee, so they suggested waiting 2, maybe up to 3 days at a time before emptying it. His bladder is naturally big and by this time it was so stretched that it wasn’t going “hard” from fullness in even that time. But it didn’t help. They start talking about putting him to sleep, about us soon running out of options. His insurance is running out, but we reiterate that we don’t care how much it costs - I’ll pay for the treatment without insurance. Rigel was registered to me and Kaytu was officially David’s, so Rigel’s costs were on me. He started off as a perfectly healthy young kitten and now he is going through this mess because someone decided to stick a catheter in him because his bladder looked big.

Baker’s plan is to do a surgery to put in an abdominal catheter and send Rigel home with it for a week, hoping this will give his bladder a chance to rest and recover function. But I’m worried - if there IS some blockage in the urethra now how would an abdominal catheter help? Won’t we just be back at square one? At this point I don’t know who to trust anymore and we are contacting anyone we can who may be able to help. What’s more, little things keep going wrong - he keeps seeing a new vet each time, and none of them seem to know all the details even though they all know his “general” story. On our last visit there a nurse feels his bladder and says it feels small. We get excited! But then the vet checks the bladder and says it’s very large and needs to be drained again. What is going on?! One place keeps coming up from multiple sources - a big university hospital (“UH”) in Uppsala. This is a large univeristy animal hospital that’s meant to be the best.

We hold off on the surgery until Monday, take Rigel home for the weekend, and call the UH small animal hospital in Uppsala. We ask if we can bring Rigel for a second opinion tomorrow and they say yes. It’s two hours away by public transport but a very kind friend of ours takes the time to drive Rigel and me there on a Sunday. He is inspected and stays there, to hopefully see an internal medicine specialist the next day.

We are cautiously optimistic - we’re told this place is the best. If anyone can fix him they can.

He stays there for…I think a week? Two? Time is now blurring together. They put him on new medication, but he is not trying to pee. This is devastating, because just the day before we brought him in he was trying but unable to go and now they say he’s not going at all!

I visit him throughout this time, catching trains to Uppsala after work. The vets are surprised that he still seems to be in such good spirits through all this - all the pills, the procedures. He has to be sedated daily to have his bladder drained with a needle, and yet he’s still active, playing, and “doesn’t even know he’s sick”. One time I visit and the nurse spends 30 minutes explaining to me how serious his situation is, how they are running out of options, and how there may be nothing we can do because the situation is so serious. Thank you very much. We did not know how serious his situation is. We just happened to stumble into what’s meant to be the best animal hospital hours from home for the heck of it. I came home and cried all night, expecting that the next day I’d get a call from the vet saying they were done.

But the next day I do get a call, and they decide to do a bladder biopsy - surgery #2 after neutering. At the same time they check for blockage by trying to pass a catheter in from the bladder out - no go. Then from the bladder in - very difficult. And finally from the bladder out again. This indicates a potential stricture. I wonder how long it’s been there?! Maybe all that time he’s been trying and unable to pee at Baker he’s actually had a stricture?

The biopsy results themselves apparently don’t show any very useful information, but after finding the stricture (and after he rips out yet another catheter), and after my potentially dumb insistence that we can’t give up now, they decide to do a Perineal Urethrostomy. Rigel has been through a lot and I didn’t want him to suffer by putting him through more procedures that he could handle, but the vets themselves in their daily updates have said that he remains a happy and active cat who is not suffering. The Perineal Urethrostomy is a big surgery that basically amputates part of the penis to remove the thinnest point with the stricture, making the genitals look more like that of a female cat. They don’t know if this will save him - they just know it may alleviate ONE of the problems. The other is his atonic and distended bladder.

It doesn’t seem to help. He yet again rips out a catheter. They wait for a few days, giving him a few hours to feel the need to pee between catheter drainings, but he just does not go. Finally they say that it’s time. They say there is nothing more they can try - the meds just don’t seem to be working, and neither did the surgery. At this point David is in Japan for work and I’m alone at home (well, not alone - our other kitten is there). I ask if it’s ok for them to drain Rigel’s bladder one last time that night so that I can come pick him up and have him spend his last day at home with his family. They say yes, but only under the condition that if he doesn’t try to pee within 24 hours he be put to sleep. I know he can go longer - Baker had waited for days at a time, but they are insistent.

At this point I have a week off of work. I had originally booked it to go away with David, but now of course I’m not going anywhere. I make arrangements and set an appointment with a veterinarian to come to our house and put him to sleep at 4:30pm on Thursday, March 12. As a last ditch effort, desperate, I look for any other veterinarians who might be able to help. Plenty of people live with handicapped pets who can’t pee on their own - they can express their bladder manually 3 times a day and the pets live happy and long lives, and sometimes bladder function even recovers. But nobody’s been able to express Rigel even after the stricture was removed and I find it difficult to understand why.

I find the website of a holistic vet Wednesday morning and email her out of desperation. It just so happens that she lives near the UH hospital and is kind enough to invite us to her house on our way home. I hate the thought of dragging him elsewhere on his last night, but it’s close and I can’t give up now. We go there. He likes her, but he is so tired and uncomfortable after all of the procedures he’s been through. The holistic vet performs acupuncture, which Rigel tolerates well at the time. We go home.

I think about how his last trip with me is on same kind of train that we took when I was bringing him home, just going from the opposite direction, in exactly the same carrier.

On the train we sit next to a kind lady who asks me questions about him and asks if she can pet him in the carrier. I say yes and she reaches in. She ends up getting off at our stop and saying that she “did some healing”. I thank her - he can use all the healing he can get. I explain to her that I’m bringing him home for his last day and she says “We’ll see what happens. It’s up to him now. Just take that cone off when you get home.”

The giant cone on his head is there so that he doesn’t lick his urethrostomy stitches and he hates it. When we get home I take it off and watch him like a hawk to stop him from licking. I spend all night awake, watching him and trying to accept that our kitten is going to die in less than 24 hours and at the same time hoping that some miracle might happen even though the vets say it can’t. Rigel is exhausted. I’m a nervous wreck. The kittens and I are locked in the bedroom with a litter box, toys, and water. I keep us all in the room so that I can stay awake and watch to make sure he’s not licking. He doesn’t mind - he’s not in the mood to play or roam anyway.

Rigel is exhausted. He tries to play with our other kitten, Kaytu, but tires quickly and sleeps.

The next morning he got up to drink some water. Then he started pacing. He’d grumble, pace, grumble, pace, lie down, eye the litter box, pace. I gently scruffed him with one hand and felt for the bladder with the other. I’m not sure if I was feeling the right spot, but I thought I felt it there, full and balloony. Shaking, I gently carried him to the litter box and dug my hand in the litter in front of him. I stepped away and waited. He sniffed around, circled slowly a few times, dug weakly, crouched, and PEED! He peed! By himself! For the first time in over a month!

I tried to get a non-blurry photo but was shaking so much it was impossible.

I was on the phone to UH immediately. I waited and waited and then excitedly told the receptionist that Rigel just peed and that I had to speak to one of the veterinarians who had treated him immediately. They told me to call them straight away if he even so much as tried to get any urine out. I wanted to make sure they knew he peed, and ask what to do next. We were only given a few days of medication and no post-urethrostomy-care instructions, I guess because they thought he wouldn’t need them. But here he was peeing, so maybe he still stood a chance!

I was told someone would call me back as soon as possible. No one called back. In the meantime I contacted the holistic vet, who was very helpful and also got a hold of relevant people at UH to let them know. They had threatened to call “the authorities” if they didn’t hear from me that he tried to pee OR from the vet who’d be putting him to sleep that it was done, so it was important for them to know.

That night I took him to Baker (which is less than 5km away) to check his bladder and make sure it wasn’t too full. They did a ultrasound and confirmed that the bladder was large, but for him at this point it may be a “normal” large. It wasn’t bursting and was still “wavy” instead of “hard”. I was told that if he goes once a day that is good enough. They represcribed some of his medication and said at this point all we can do is wait and see what happens because if he stops peeing there’s not really much they can do anyway, since they’ve already tried everything else. It was terrifying.

The next day I thought I’d made a terrible mistake. I felt Rigel’s bladder in the morning - sometimes he peed right after I palpated it gently, so I did and then picked him up to carry him to the litter box. He started convulsing in my arms. I put him down, terrified that his bladder had ruptured and he was dying. I was on the phone with the taxi, calling to take us to Baker to put him to sleep because I thought he was dying in pain in the house. I was afraid it would be too late. But then he vomited what smelled like the food we were given at Baker for me to give him with his medicine the previous night (the next dose was due while we were there, so they gave me a packet of Hills food to give him with it). Maybe the new food just didn’t agree with him…anyway, after vomiting he seemed better. I put him in the box gently and he peed again, then demanded food.

I again called UH, again to no response. I thought they’d be happy that he started peeing, but they seemed to want nothing to do with Rigel anymore. I’m not sure why…I wanted to thank them for everything they’ve done for him, for the surgery, etc, but I still haven’t heard back from them since.

And so it went on. As soon as it started to get dark I’d start counting the hours. It was the worst week of my life but I know it would have been a million times worse for Rigel himself. Sometimes Rigel would pee twice a day, and then suddenly he didn’t pee for 25 hours. It fluctuated wildly. The holistic vet came and looked at him last Sunday - she said he looked much better than when she first saw him and said that his bladder felt good.

We spent the days in the sunniest rooms, where the cats preferred to be. I’d spread out some blankets on the floor and watch him, and take quick naps when he did. We were both so tired.

Last Monday we had an appointment back at Baker to bring him in and see how to proceed. After all, we had no idea what the heck happened. His meds were running out within days and I had no idea where to go from here. We managed to get an appointment with the internal medicine specialist at Baker - the first time we’ve actually met one of these specialists in person. She was very nice, looked through his gigantic journal history, and represecribed the medications. She explained what she thought had happened, and also that the bladder, like any other organ, can be a highly individual thing. Just because it may look more elongated or larger than normal doesn’t mean you necessarily have to start poking around in there if no problems had been reported. They took a blood test and instructed me to get a urine sample to test for organ failure, infection, etc. She said he looked like a…normal cat. By that point he seems to have rested from the long and stressful hospital stay and was feeling more himself, playing, exploring.

At this point I’d been running on maybe 7 hours of sleep since the previous Wednesday and feeling loopy. I was dragging Rigel’s pee clumps around to the vet in case they were relevant, hadn’t had a hot meal in days, and all of my pants were now falling off of me. That afternoon the vet called with blood test results - kidneys were functioning well, he had slight anemia (she said normal after surgery) and potentially a sign of either some mild allergy OR a parasite. She said normally in this case they’d deworm the cat, but he’s already balancing so many medications that they didn’t want to risk it just then.

She also said to take him off of one of the meds - Stesolid (Valium). I was scared to make it worse, but I know he can’t be on these high dosages and medications forever, so we stopped with the Stesolid.

I managed to get a urine sample by holding a measuring cup under him a couple of mornings later. I brought it in to the hospital at about 5am. The specialist called us the next day saying there was no obvious sign of infection, the urine concentration and pH level looked good, and the one slightly concerning thing she saw was some bladder cells coming out in the urine. She says that also may be normal after surgery, but I need to submit another urine sample this upcoming Monday to recheck.

David also got home from his work trip Tuesday night, and made me my first warm meal in a week. He’s been helping to watch him while I’ve had to go back to work since.

The holistic vet looked through his UH journals and confirmed that the stitches they used for the urethrostomy were non-dissolving and need to be taken out in 10 days. That was day 10, so I made an appointment to have them looked at and potentially removed the next day (yesterday). It was much rougher than I thought! The vet just ripped off his scab down there with her hand - it started bleeding. Then bled some more while they were cutting the stitches around his urethral opening. I had read that one serious potential complication of the urethrostomy is tissue healing over the opening, creating yet another obstruction, and I was worried that continued damage to the area like this could make this worse! We were told he’d have to not lick that area for another 7 days at least until it healed, and even afterwards we’d have to watch him to make sure he’s not obsessively licking there (but he’s never been an obsessive licker, at least not until now). Rigel was so good throughout, though. I had to help hold him in the front while the nurse removed the stitches. He just wrapped a foreleg around my arm, buried his face in my sweater, and cried. The area there is tender now, but he’s peed twice since he got back…we are watching like hawks for any signs of him straining or having other trouble.

Rigel has become adept at removing his cone by himself. I ended up getting a harness from a pet store and tying the bandage-tie around his neck to that, so that he can’t pull it over his head.

When I come home now he bolts down the stairs to meet me - he used to meet me at the top of the stairs, but now he trots down to the door and looks up at me for scratches. It’s always so nice to see him.

I’m still so scared of something going wrong - bladder getting worse, stricture post-surgery, something. Nobody knows what’s going to happen. It’s been just over a week since the day he was meant to be put to sleep and he seems to be feeling much better and acting very playful and affectionate. He is still having some constipation but we’re managing that. But I’m afraid of getting too confident - things can go downhill at any time, and so many things have already gone wrong. But at least he’s still here now - still happy, relaxed, and (please knock all the wood in your house for this one) still peeing.

Tackling Gastropoda’s Memory Usage - Round One

I’ve been having to power cycle my Gastropoda Digital Ocean droplet every day lately because something was hogging up all the memory. It was a little annoying to diagnose because I know the problem had to do with the recurring events that are run using a cron job, of which there are quite a few. I know ideally PHP isn’t something you would use for a long running server process, but these jobs weren’t long running - they were just frequent.

The problem ended up being something very obvious. Every minute I run an event called CheckIdleSnailActions. It loops through all living snails in the world and sees what they’re doing. Unfortunately at some point that event began to take more than a minute to run. So when I ran ps aux --sort -rss to sort all running processes by memory usage I saw two instances of CheckIdleSnailActions running at the same time, one taking something like 60% memory and the other ~30%.

I always knew running these checks every minute would see me encounter issues like these and know I need to find a more efficient way of doing this. In the meantime, the first step was to avoid running these events in parallel.

To do this I ended up using lock files. The cron job runs a Dispatcher command called RunRecurringEvents. This command grabs all recurring events that are scheduled to run at this time from the database and runs them. These events include things like:

  • CheckIdleSnailActions
  • TickOrientationToNeutral
  • TickStressToNeutral
  • DepleteEnergyByTime
  • …etc

I created a new directory in app/storage called lockFiles.

I didn’t want to stop all of these things from running in parallel for now. The heaviest event by far is CheckIdleSnailActions. This is where we generate the brain, where the neurons do their work for each snail, where we comb through each snail’s memories for recognition of an object, retrieve and record mood impact, etc.

When each recurring event is triggered it runs only if there is no lock file matching the event’s name in app/storage/lockFiles. So if there is a file called app/storage/lockFiles/CheckIdleSnailActions.txt, the event will not run. If there is no lock file by this name it creates one and starts running, and when it finishes running it just deletes the lock file and allows the next instance of the event to run when it is triggered.

So far I haven’t had to power cycle the droplet since this went in. It’s not a full solution (I can’t even imagine how long it might take for one CheckIdleSnailActions event to run if the world ever has thousands of living snails in it), so this is a work in progress.