The Circle of Life

Dear Tim,

It’s been a while but that doesn’t mean I’ve been sitting on my hands doing nothing. I’ve just been watching, waiting, improving. The usual developer affair. If you’ve been following, you might’ve seen there was a Contact page added as I realised I needed a better interface for allowing people to raise issues or suggestions, rather than it be wholly cut off. Currently it’s under the name Esmeralda Hart, the official spokesperson for Kalmany to keep that look of a system that thinks its real.

Meanwhile, I’ve been doing more coding and modifying. After all my refactoring I’m considering refactoring a step further and splitting my current SQLAlchemy module (because it’s big now. All the constituency logic takes up about a thousand lines. The whole file is four thousand lines if that’s anything to go off). I also stupidly started specifying ID names in the classes – a stupid, stupid idea that’s added repetition into my code. I’ll need to look at the python standards for naming.

Anyway, to the good stuff – I’ve been adding new logic as part of my Time Update. I’ve cut back on the state machine idea I had and reduced it down to a simple death ticker but that still took a lot of work. I had to work into the fact citizens could die, and what would happen when they did. I’ll probably need to have that taken into account in the API too. Nonetheless, I’ve managed to build in five major things: Aging, Births, Deaths, Dating and Relationships. The final thing will be immigration, but I may leave that for after this update.

Aging was a simple task; each citizen ages a year after three days. The only requirements were that the age would change, and their age group. The age group already had a function to recalculate so I just run that alongside the age along.

Births were more difficult, but we’ll come to that. First, let’s go into Deaths.

Deaths operate in a state machine form. Each citizen has a status of ‘Chilling‘ (it sounds better than just ‘Alive‘) and from that state they evaluate if they change. They have two states they can move into: ‘Sick‘ or In ‘Danger‘. Sickness is caused by age: as they get older, the chances of sickness increase exponentially.

def getWarmth(self):
        if self.ethnicity.isLizard():
            warmth = math.floor(math.exp(self.age / 9))
        elif self.ethnicity.isHumanoid():
            warmth = math.floor(math.exp(self.age / 18))
        elif self.ethnicity.isVampire():
            warmth = math.floor(math.exp(self.age / 36))
        else:
            warmth = 0
            
        return warmth

Ignore the reference to Warmth for now. As you can see, it also varies based off ethnicity. This prevents Zombies and Androids from getting sick. These values mean the chance of sickness at age 16, the youngest a citizen can be, is around about 1%. At 90, this becomes close to 100%. But at 50, the halfway point, it’s more like 10%. So there’s a definite curve, and it becomes dangerous as you get older. Danger is different:

 def getDangerLevel(self):
        dangerLevel = math.floor(math.exp(self.age / 18))
        return dangerLevel

…Old people are stupid; I don’t know. But everyone is on the same level for danger.

Now when Sick or In Danger, a citizen enters that state but only dies based on another condition: if they’re sick, they rely on the constituency’s healthcare rating, and its disease transmission rate:


    def evaluateBeingSickStateChage(self):
        gen = random.random() * 100 
        diseaseTransmissionRateThreshold = (100 * self.citizen.constituency.getDiseaseTransmissionRate()) - 100
        print("Disease Transmission Rate: ", diseaseTransmissionRateThreshold)
        if gen < self.citizen.constituency.getHealthcareRating().rating and gen > diseaseTransmissionRateThreshold:
            print("Citizen has gotten better")
            self.setStateChilling()
        else:
            print("Citizen has died")
            self.setStateDead()

To summarise, the constituency needs to have a good healthcare rating to get better, else they’ll die. This, combined with often getting sick, means the aged will die quicker. However, the DiseaseTransmissionRate provides a kind of baseline amount of people that will die regardless of how good the healthcare is. Because sometimes life sucks.

The same is true for being in danger, but we use the security rating instead – the fire service, the coastguard, the police service. And the DeathHazardRate acts like our DiseaseTransmissionRate.

All in all it needs to some testing to see what Kalmany’s death rate ends up looking like.

Now let’s look at Dating and Relationships. We’ve new objects to determine a relationship between two citizens. Dating is the first step to create the object. For this, we run through a citizen’s Dating Pool, and pick a random person to go on a first date. We then check for a few qualities:

  • Are either of them in a relationship? No point dating someone who “has a boyfriend”.
  • Do they match in sexualities? A hetero man wouldn’t bother to be in a relationship with a homo man. (Note, their identified sexuality might not match their actual biological sexuality so we don’t use the sexuality object for this evaluation).
  • Does either one have a “red flag” for the other – a personality trait that puts them off. This means we get an interesting simulation of compatibility and is much better than trying to match personalities in the get-go, which isn’t indicative of real life.
if not self.isInRelationship():
            print("Finding the citizen's dating pool")
            datingPool = self.getDatingPool()

            if len(datingPool) == 0:
                print("No one is in this citizen's dating pool")
                return []

            crush = random.choice(datingPool)
            print("They have a crush!")
            if not crush.isInRelationship():

                if self.hasAttractionForCitizen(crush) and crush.hasAttractionForCitizen(crush):
                    print("The two are attracted one another")
                    if not (self.hasProblemForCitizen(crush) or crush.hasProblemForCitizen(self)):
                        print("The two are compatible with one another")
                        print("They are now dating!")
                        return self.newRelationship(crush)
                    else:
                        print("They have decided they are not compatible")
                else:
                    print("The two are not attracted to one another")
            else:
                print("Unfortunately the crush is already in a relationship")
        else:
            print("Citizen is already in a relationship")
        return []

So now they’re dating! And we enter a relationship that has its OWN state changer! We track when they’re Dating, Cohabiting, Married, and then if they Break Up, get Divorced, one of the dies leaving the other Widowed, or both are death an the relationship only exists in the Afterlife.

The way this state change works is it simulates the two people spending time together – we pick a personality trait that generalises how they’re acting, and if its a negative trait, it puts down the partner’s happiness.

        submitterPersonality = random.choice(self.submitter.personalities)
        if submitterPersonality.personality.parity > 0:
            self.dominatorHappiness += 3
        else:
            self.dominatorHappiness -= 4

        dominatorPersonality = random.choice(self.dominator.personalities)
        if dominatorPersonality.personality.parity > 0:
            self.submitterHappiness += 4
        else:
            self.submitterHappiness -= 3

In a relationship we have a Submitter and a Dominator (you can figure that out), and you can see the sub is harder to make unhappy than the dom. The dom is harder to make happy than the sub. If their happiness reaches a certain level, we move to the next state, which is also decreases the threshold that says they’ll break off the relationship. Basically, if they’re Dating, it’s easier to break up than if they’re Cohabiting or Married. And when I say easier, they’re less inclined too – even if they’re much more unhappy. I think this simulates a real relationship where if you’ve had some real good times, you’re less inclined to let a bad day ruin it.

So this continues, but for every relationship we then evaluate Births. Every citizen is “born” at 16 so we only care about citizens older than 34 (else they’re having a kid younger than 18 and I’m not promoting underage sex). We check this, and we check their ethnicity compatibility. We also check their sexes (sorry but only male and female relationships will birth a kid. I’m not tracking adoption as much as that would be cool. We’re going to assume even tho parents may divorce, they were probably good parents…).

If they satisfy these conditions, we birth a citizen at 16 (or in other words, recognise that a citizen was born 54 days ago and now can go on the voting register). Or at least, we WOULD, but you see there’s nothing so far to prevent every evaluation to produce a citizen. No couple is having seventeen kids. Sorry, no SANE couple is having seventeen kids. So we put a little logic to determine how many kids they already have and as that goes up, the chances they have another go down.

If that’s good, we birth them. And we evaluate a bit of logic to determine their qualities:

  • Name; the kid takes the mother’s last name (we’re a matronymic society in Kalmany, why not?)
  • Age is always 16.
  • Likes and Dislikes are random, they can like and dislike whatever they want
  • Industry or their profession is random too. We could influence this, but again, they can be whatever they want to be.
  • Ethnicity is based off a simplified bit of gene logic. Each ethnicity has a strength in the gene pool, and the mother and father’s ethnicity produce weights to their own ethnicity. The kid chooses one based off those weights. So although a Vampire is lower than a Human, they could end up Vampire on some occasions.
  • Religion; a little more tricky. I went with a simple relation on the mother and father’s religions; whoever’s has the most followers in their constituency the kid follows. Religions will change anyway so it’s not a huge problem. I could have added this as a factor into the dating pool but I decided the Kalman’s were open-minded people.
  • Sex is just a random trinary (though weighted toward male and female).
  • Sexuality and Gender are set as their sex and hetero in the beginning BUT (heavy but) we re-evaluate them after we generate the kid’s personalities and biological attractions (just the way the DB works, they need an initial value).
  • Their Zodiac sign rotates every birth cycle. Nothing special here.
  • Finally, their Personality is a combination of 4 qualities from the mother, 4 from the father, and 2 wildcards. This means some personality traits may become more amicable over the long-term but we’ll have to see. It’s all a bit random.

With that, we chuck them in the system and we’ve got a new citizen to live and breath! Kalmany enters the realm of time!

I’ll need to do some rigorous testing – with the introduction of death, I need to make sure candidates and members of the assembly aren’t dead. People can run if their dead. I also need to make sure I don’t kill off more people than I birth so I’ll need to rigorously run the procedure to ensure we’ve got a roughly equal birth/death ratio. It’s going to be a lot of runs.

I’d say it’s got a week until release. When it does get implemented, I’d like to add a new page for the Citizen Register to display specific citizen data, rather than leaving that specific to the candidates. I could also add some fancy logic to produce family trees as we are tracking mothers and fathers (which will need to be considered in the dating pool. Can’t have family dating, that’d be weird.)

The hardest and most troubling part of this whole systems is how I’m tracking time. I can’t run using the date today anymore! So Kalmany has its own clock now for monitoring what day it is. I may have to introduce something to ensure this is linked to the elections and assemblies for now.

Anyway, hope you’re doing okay. I haven’t heard from you in a while. I’m concerned the experiment to freeze you in ice worked too well and that you’re going to be in stasis for a hundred years. I get why they want to do it – so we can see if we invented time travel in the future, but I don’t see why it has to be you. You weren’t scared when you went under. That’s honourable. Your sister is worried anyhow, I’ll keep sending her the letters you gave me.

Yours,
Stan

Leave a comment