All nine parts
Rubbish is not glamorous, but anyone who has lived in a busy city knows it decides how a street feels. In Naijapolis it is a full system: the town makes refuse, something carries it away, and when that fails, the consequences ripple outwards into health, floods, mood and the vote. It is a good small example of how a game system is built.
A stock and a flow
The whole system is one number that remembers: the pile. Each month the town adds refuse to it, the collectors take some away, and what is left stays for next month. That is a classic "stock and flow" model, and it is the same shape as a bath filling while the plug is half open.
The rules live in one pure file with no browser in it, so the whole monthly step is a function: state and a few facts about the city go in, and what happened comes out. That is the same discipline as Part 2, and it is why the system has its own test file of twenty-three tests.
What is made
Refuse comes from people and from shops: a little per resident, and rather more per shop. A town of a hundred homes and a few market stalls simply makes more than a village. Two things change what is in the pile, not just how much of it there is.
Shopping adds plastic: the more of the town is trade, the larger the plastic share (up to a ceiling). A single-use plastic ban cuts it in half, at the price of grumbling from traders. That one share matters later, because plastic is what chokes drains.
What is taken out before the street
Real cities have informal recyclers, and so does the game. Before refuse reaches the street, a share of the recyclable part (plastic and metal) is collected by scavengers. Organise them into a cooperative and that share rises from about an eighth to about a third. A buy-back centre pays for more, and a compost heap turns organic waste into a little money. Doing the right thing is, here, also a revenue stream.
What carries it away
Capacity is the sum of three things: the bins, skips, depots and lorries a player has placed; the city's collection contract; and the sanitation budget, which scales the lot between half and one and a half times. There are three contracts, and each is a small character:
Notice the stub on the cart pushers' bar. They will not enter a flooded street, so their capacity drops to forty percent whenever the town floods. That one rule turns a simple stock into a feedback loop:
// one month, heavily simplified
const made = people * PER_PERSON + shops * PER_SHOP;
const net = made - recycled - composted; // what reaches the street system
const capacity = (carriers + contract.cap(people) * (flooded && carts ? 0.4 : 1)) * sanitationBudget;
const carried = Math.min(pile + net, capacity);
pile = (pile + net - carried) * (1 - crewClearShare); // a sanitation day clears a slice
// drains choke toward a target; they choke fast and clear slowly
const target = clamp(max(0, heap - 0.2) * 1.4 + (flooded ? 0.25 : 0) + plasticPart, 0, 1);
clog += (target - clog) * (target > clog ? 0.5 : 0.25);
The drains follow the pile with a delay, easing toward a target: quick to choke, slow to clear. It means a bad month is felt for a while, and a good policy takes time to show. A choked storm drain also fails more often when rain comes, which links the system to the flood rules from Part 3.
The landfill
Landfills have a throughput and a total room. Each month they take what they can and fill up; when full they take nothing, and the capacity they provided is removed from the city's sum. The town gets a toast and a letter from the sanitation engineer. Nothing crashes: the simulation just does what a real town would do, and the heaps grow.
When it goes wrong, who tells you
Consequences arrive through the channels the player already knows. Residents write letters, with wording that gets worse as the pile grows ("the refuse has not been collected for weeks and it is blocking the road"), and no more than one every few months, so a bad stretch does not become spam. Mood drops once the heap passes a threshold. Dirty-water outbreaks become more likely, and an open dump can catch fire. Only past a high threshold, and only after the town is six months old, does it cost votes.
That grace period is deliberate. A new player who has not yet found the waste page should not be punished for a system they have not met. It follows the same rule as the stages in Part 2: the waste system does not even run until the town has grown to the step where it makes sense.
Policies: every choice has a cost
Five policies are on offer, each a small data record with its costs and consequences visible before it is chosen:
| Policy | What it does | What it costs |
|---|---|---|
| Awareness campaign | Less refuse is made, and it is cleaner | A per-resident monthly cost |
| Monthly sanitation day | Volunteers clear part of the pile each month | A small cost, and a little goodwill gained |
| Fines for dumping | Less illegal dumping, some revenue | A little goodwill lost |
| Household refuse levy | Brings money in | People grumble |
| Plastic ban | Halves the plastic in the mix | Traders complain |
The policy table is pure data: a few numbers each. The engine reads whichever are switched on and combines them, so adding a sixth policy is a new row, not new code.
Seeing the problem
All of this would be invisible without something to look at. The game draws the refuse as heaps of bags and boxes at street corners and beside homes and shops. The more the city has fallen behind, the more heaps there are, up to a cap. Each heap is built from a small deterministic hash of its index, so the same heap always looks the same: a dark stain on the ground, a mound, tiers of bags in grubby blacks and browns with a few scraps of bright plastic blown around it.
// a tiny hash turns an index into a repeatable 0..1 number; no random state to save
const hash = n => { let h = n | 0; h = Math.imul(h ^ (h >>> 16), 0x45d9f3b); h = Math.imul(h ^ (h >>> 16), 0x45d9f3b); return ((h ^ (h >>> 16)) >>> 0) / 4294967296 };
const angle = hash(i * 7 + 1) * 6.283; // which side of the home
const reach = base + hash(i * 13 + 5) * 1.4; // how far out
const count = 9 + Math.floor(hash(i * 3 + 9) * 6); // how many bags
The piles are decoration that only reads the city; they never change it. They also respect the "reduced motion" setting, so the little tween when heaps appear is skipped for players who ask for calm.
What we learned
- A stock that remembers makes consequences feel earned: nothing happens at once, and everything happens eventually.
- Feedback loops are the engine of a simulation. One tiny rule (carts avoid floods) turned a plain model into a story.
- Give a new player a grace period, and let a system wait until it makes sense.
- Put every policy in a table and show its price and effect before the player chooses it.
- Make the invisible visible: heaps in the street do more than any percentage in a menu.
That is the series
Nine parts, one game: one file to carry it, a kernel that can be tested, a simulation that can be seen, a renderer that can adapt, a soundtrack that keeps time, data that is never believed, a pipeline that plays the game before you do, a garden that grows from a seed, and a town that has to deal with its own rubbish. If you build something with these ideas, we would love to hear about it at [email protected]. And if you want to see all of it working, the game is one tap away.