Service Coverage
How figures register services with houses, how scores decay, and how raw flags are aggregated into the values that the evolution check reads.
Pipeline Overview
[Figure walks road]
│ figure::provide_service() called each step
│ finds nearby houses via dcast_house()
│ sets service flag on runtime_data (e.g. housed.school = MAX_COVERAGE)
▼
[End of month — city_houses.cpp]
│ house_service_calculate_culture_aggregates()
│ reads raw flags → writes aggregate scores
│ (entertainment, education, health, num_gods)
▼
[house->evolve() called]
│ check_requirements() reads aggregate scores
│ compares against model thresholds
▼
[decay_services() called]
decrements all service flags by 1
(flags reach 0 = service absent next month unless renewed)
Walker Registration
Service figures inherit from figure_service_walker (or a similar base). During their movement tick they call provide_service(), which scans the tiles within range and stamps the relevant flag on each house found:
// Example: scribal school teacher (src/figure/service.cpp)
void figure_teacher::provide_service() {
int range = 2; // tiles
for each tile within range of current position:
building* b = map_building_at(tile);
if (auto* house = b->dcast_house()) {
house->runtime_data().school = MAX_COVERAGE;
// MAX_COVERAGE is typically 4 — decay takes 4 months
// to reach 0, giving walkers time to re-visit
}
}
The flag value MAX_COVERAGE is not a boolean — it is a counter. decay_services() decrements it each month. A house covered last month still has a positive flag this month, giving the walker a grace window before the flag drops to zero and coverage is lost.
Monthly Decay
// building_house.cpp
void building_house::decay_services() {
auto& d = runtime_data();
// each flag: if > 0, decrement
if (d.school) d.school--;
if (d.library) d.library--;
if (d.academy) d.academy--;
if (d.physician) d.physician--;
if (d.dentist) d.dentist--;
if (d.apothecary) d.apothecary--;
if (d.mortuary) d.mortuary--;
if (d.magistrate) d.magistrate--;
if (d.booth_juggler) d.booth_juggler--;
if (d.bandstand_juggler) d.bandstand_juggler--;
if (d.bandstand_musician) d.bandstand_musician--;
if (d.pavillion_musician) d.pavillion_musician--;
if (d.pavillion_dancer) d.pavillion_dancer--;
if (d.senet_player) d.senet_player--;
if (d.bullfighter) d.bullfighter--;
// shrine_access and bazaar_access are set by proximity checks,
// not by walker flags — they do not decay
}
Aggregate Computation
Raw service flags are not read directly by the evolution check. Once per month, before evolve() is called, house_service_calculate_culture_aggregates() translates flags into the four aggregate scores that the model thresholds compare against.
Entertainment Score
Entertainment is a weighted sum of the four performer types. Each type contributes a share of the house's score based on a per-tier divisor from config:
// city_houses.cpp int entertainment = 0; if (d.booth_juggler) entertainment += (d.booth_juggler * 10) / model.entertainment_juggler_divider; if (d.bandstand_juggler) entertainment += (d.bandstand_juggler * 10) / model.entertainment_juggler_divider; if (d.bandstand_musician) entertainment += (d.bandstand_musician * 10) / model.entertainment_musician_divider; if (d.pavillion_musician) entertainment += (d.pavillion_musician * 10) / model.entertainment_musician_divider; if (d.pavillion_dancer) entertainment += (d.pavillion_dancer * 10) / model.entertainment_dancer_divider; if (d.senet_player) entertainment += (d.senet_player * 10) / model.entertainment_senet_divider; if (d.bullfighter) entertainment += (d.bullfighter * 10) / model.entertainment_dancer_divider; d.entertainment = std::clamp(entertainment, 0, 100);
The divisors are defined per housing tier in houses.js. Higher-tier housing requires more diverse entertainment to reach the same score, meaning a single juggler booth is not sufficient past a certain point.
Education Score
Education uses a stepped model rather than a continuous score:
// city_houses.cpp
int education = 0;
if (d.school || d.library)
education = 1;
if (d.school && d.library)
education = 2;
if (d.school && d.library && d.academy)
education = 3;
d.education = education;
| Score | Condition | Required for |
|---|---|---|
| 0 | No school or library coverage | Tiers 1–10 |
| 1 | School OR library | Tier 11 (Common Residence) |
| 2 | School AND library | Tier 17 (Elegant Manor) |
| 3 | School AND library AND academy | Not required by any tier in base game |
Religion Score
Religion counts the number of distinct gods whose temple walkers have visited the house within the last few months:
// city_houses.cpp
int num_gods = 0;
if (d.temple_osiris) num_gods++;
if (d.temple_ra) num_gods++;
if (d.temple_ptah) num_gods++;
if (d.temple_seth) num_gods++;
if (d.temple_bast) num_gods++;
// Shrine provides minimal coverage when no temple is present
if (d.shrine_access && num_gods == 0)
num_gods = 1;
d.num_gods = num_gods;
Each of the five gods (Osiris, Ra, Ptah, Seth, Bast) contributes independently. High-tier housing requires access to multiple gods — typically 2 gods for Fancy Residence, 3 for Elegant Manor. The player must build temples to all required gods and ensure walker paths cover the housing area.
Health Score
// city_houses.cpp int health = 0; if (d.apothecary) health++; if (d.physician) health++; d.health = health; // 0, 1, or 2
| Score | Condition | Required from |
|---|---|---|
| 0 | No health coverage | — |
| 1 | Apothecary or Physician | Tier 8 (Spacious Homestead) |
| 2 | Both Apothecary and Physician | Not required directly — Dentist is the gating service at higher tiers |
Note: the Dentist is tracked via a separate boolean flag (d.dentist) and is checked independently from the health aggregate — it is not folded into the health score.
Water Supply
Water is not an aggregated score but a direct grid property plus a proximity check:
// Water access levels: // 0 — no water // 1 — well within range (base.has_well_access) // 2 — water supply building within range (base.has_water_access) // Tiers 1–4 accept level 1 (well) // Tiers 5–20 require level 2 (water supply)
The distinction between well and water supply is enforced per-tier in has_required_goods_and_services(). Placing a well next to a tier-5+ house will not satisfy the water requirement — only a proper Water Supply building qualifies.