Levels

The town_update_population_levels function at 0x0051C650 determines how many citizens are promoted and demoted and how many poor citizens emigrate.

The following pseudocode denotes the calculation:

class Town:
    citizens: list[int]
    satisfactions: list[int]
    has_mint: bool
    dwellings_capacity: list[int]

    def __init__(
        self,
        rich: int,
        wealthy: int,
        poor: int,
        satifsaction_rich: int,
        satisfaction_wealthy: int,
        satisfaction_poor: int,
        has_mint: bool = False,
        dwellings_capacity_rich: int = 999999,
        dwellings_capacity_wealthy: int = 999999,
        dwellings_capacity_poor: int = 999999,
    ):
        self.citizens = [rich, wealthy, poor]
        self.satisfactions = [
            satifsaction_rich,
            satisfaction_wealthy,
            satisfaction_poor,
        ]
        self.has_mint = has_mint
        self.dwellings_capacity = [
            dwellings_capacity_rich,
            dwellings_capacity_wealthy,
            dwellings_capacity_poor,
        ]

    def update_population_levels(self):
        self.update_population_level(0)
        self.update_population_level(1)
        # TODO poor emigration

    def update_population_level(self, level: int):
        target = (
            self.citizens[2]
            * (self.satisfactions[level] + 40)
            // self.get_divisor(level)
        )
        target = min(max(target, 1), self.dwellings_capacity[level])
        LOGGER.debug(f"{level} target: {target} stock: {self.citizens[level]}")
        if target < self.citizens[level]:
            # Current stock exceeds target
            demoted = 2 * self.citizens[level] // target + 1
            if demoted > self.citizens[level]:
                demoted = self.citizens[level] - 1
            self.citizens[2] += demoted
            self.citizens[level] -= demoted
        else:
            # Target exceeds current stock
            if self.citizens[level]:
                promoted = 2 * target // self.citizens[level] + 1
            else:
                promoted = 1  # Avoid division by zero
            if self.citizens[2] > promoted:
                LOGGER.debug(f"promoting {promoted} poors to {level}")
                self.citizens[2] -= promoted
                self.citizens[level] += promoted

    def get_divisor(self, level):
        if level == 0:
            return 213 if self.has_mint else 320
        elif level == 1:
            return 160
        raise Exception()

For a fixed number of total inhabitants and satisfactions, the groups converge: image