Wiki / Developer Docs / Housing System / Config Format

Config Format — houses.js

Complete annotated reference for the housing config file at src/scripts/houses.js.

File Structure

houses.js is a JavaScript file executed by the embedded MuJS VM at startup. It calls building_config.add() once per housing tier, passing a config object. The 20 calls appear in tier order (Crude Hut first, Palatial Estate last).

// src/scripts/houses.js  (structure overview)

building_config.add({
    type: "BUILDING_HOUSE_CRUDE_HUT",
    // ... (outer fields)
    model: {
        // ... (per-difficulty arrays, 5 elements each)
    }
});

building_config.add({
    type: "BUILDING_HOUSE_STURDY_HUT",
    // ...
});

// ... 18 more entries

Annotated Example

The example below is based on Ordinary Cottage (tier 6) and covers every field present in the config.

building_config.add({
  type: "BUILDING_HOUSE_ORDINARY_COTTAGE",

  // ── Visuals ──────────────────────────────────────────────────────
  animations: {
    base: { pack: 18, id: 5, offset: 10 },      // spritesheet location
    preview: { pack: 18, id: 5, offset: 0 },    // shown while placing
    house: { pack: 18, id: 5, offset: 10 },     // in-game sprite
    minimap: { pack: 18, id: 5, offset: 0 }     // minimap dot sprite
  },
  variants: {
    // animation variant sets for different merge states
    single: { ... },   // 1×1 unmerged
    merged: { ... }    // 2×2 merged
  },

  // ── Placement ─────────────────────────────────────────────────────
  building_size: 1,     // base footprint in tiles (always 1 here; size
                        // grows via merge/expand, not config)
  can_merge: true,      // allows 4 adjacent same-tier plots to merge
                        // into one 2×2 block; false for manors/estates

  // ── Neighbourhood influence ───────────────────────────────────────
  desirability: {
    value:     -1,      // base desirability contribution to neighbours
    step:       1,      // change per tile of distance
    step_size:  1,      // apply step every N tiles
    range:      2       // maximum influence radius (tiles)
  },

  // ── Crime ─────────────────────────────────────────────────────────
  crime: {
    base:  5,           // base crime-generation value
    value: 0            // additional scaling factor
  },

  // ── Per-difficulty model ───────────────────────────────────────────
  // All arrays have 5 elements: [Very Easy, Easy, Normal, Hard, Very Hard]
  model: {

    // Desirability thresholds
    devolve_desirability: [-20, -15, -10,  -5,   0],
    evolve_desirability:  [ -5,   0,   5,  10,  15],

    // Population
    max_people:           [ 18,  17,  15,  13,  10],
    // (per tile; multiply by footprint area for total)

    // Tax
    tax_multiplier:       [  9,   8,   8,   7,   7],
    prosperity:           [  5,   5,   5,   5,   5],
    // prosperity points added to city score each year this house exists

    // Food
    food_types:           [  1,   1,   1,   1,   1],
    // number of distinct food types required

    // Services — all boolean (0 or 1)
    water:                [  1,   1,   1,   1,   1],
    // 1 = Water Supply required (not just well)
    religion:             [  0,   0,   0,   0,   1],
    // number of gods required (0–5)
    education:            [  0,   0,   0,   0,   0],
    // education level required (0–3)
    physician:            [  0,   0,   0,   0,   0],
    dentist:              [  0,   0,   0,   0,   0],
    health:               [  0,   0,   0,   0,   0],

    // Entertainment threshold (0–100)
    entertainment:        [  0,   0,  10,  15,  20],
    // Entertainment type divisors (higher = harder to reach threshold)
    entertainment_juggler_divider:  [ 4, 4, 4, 5, 5],
    entertainment_musician_divider: [ 3, 3, 3, 4, 4],
    entertainment_dancer_divider:   [ 3, 3, 3, 3, 3],
    entertainment_senet_divider:    [ 2, 2, 2, 2, 2],

    // Goods (0 = not required, 1 = required)
    pottery:  [0, 0, 0, 0, 0],
    beer:     [0, 0, 0, 0, 0],
    linen:    [0, 0, 0, 0, 0],
    jewelry:  [0, 0, 0, 0, 0],

    // Risk values (0–100; higher = more frequent incidents)
    fire_risk:    [15, 16, 18, 20, 22],
    // NOTE: fire_risk peaks at Ordinary Cottage, then decreases at higher tiers
    damage_risk:  [ 5,  5,  5,  5,  5],
    disease_risk: [ 8,  8,  8,  8,  8],
    malaria_risk: [ 5,  5,  5,  5,  5],
    crime_risk:   [12, 12, 12, 12, 12],

    // Food / goods consumption rates
    food_consumption_percentage: [100, 100, 100, 100, 100],
    // % of max_people / month consumed as food
    food_storage_multiplier: [3, 3, 3, 3, 3],
    // house stores up to (multiplier × monthly consumption)

    // Devolution stability
    devolve_delay: [2, 2, 3, 3, 4],
    // months of consecutive unmet requirements before devolution applies

    unreachable_ticks_devolve_threshold:    [12, 10, 8, 6, 4],
    // ticks with no walker access before forced devolution
    unreachable_ticks_block_evolve_threshold: [4, 4, 4, 4, 4],
    // ticks with no walker access that block evolution (even if other needs met)

    days_without_food_devolve_threshold: [30, 28, 25, 20, 15]
    // days without food delivery before devolution
  }
});

Difficulty Indexing

Every array in model{} has exactly 5 elements, indexed by the current difficulty setting at runtime:

IndexDifficultyEffect on housing
0Very EasyLowest desirability thresholds, most forgiving food/service requirements.
1EasySlightly tighter than Very Easy.
2NormalBaseline. Values in the player-facing wiki are shown for this index.
3HardHigher entertainment and desirability thresholds.
4Very HardStrictest requirements, highest risks.

In C++ the difficulty index is read via game_settings().difficulty and passed into every model.field[difficulty] lookup inside check_requirements().

model{} Field Reference

FieldTypeDescription
evolve_desirabilityint[5]Minimum desirability to evolve to the next tier.
devolve_desirabilityint[5]Desirability below which devolution is triggered.
max_peopleint[5]Maximum residents per tile. Multiply by footprint area for total.
tax_multiplierint[5]Scales the base tax rate for this tier.
prosperityint[5]Prosperity points contributed to the city score annually.
food_typesint[5]Number of distinct food types required (1 or 2 in base game).
waterint[5]0 = well acceptable, 1 = Water Supply building required.
religionint[5]Number of distinct gods required (0–5).
educationint[5]Education score required (0–3).
healthint[5]Health score required (0–2).
physicianint[5]1 = physician walker coverage required.
dentistint[5]1 = dentist walker coverage required.
entertainmentint[5]Entertainment score threshold (0–100).
entertainment_juggler_dividerint[5]Juggler contribution divisor. Higher = less score from jugglers.
entertainment_musician_dividerint[5]Musician contribution divisor.
entertainment_dancer_dividerint[5]Dancer contribution divisor.
entertainment_senet_dividerint[5]Senet player contribution divisor.
potteryint[5]1 = pottery required in inventory.
beerint[5]1 = beer required.
linenint[5]1 = linen required.
jewelryint[5]1 = jewelry required (luxury good).
fire_riskint[5]Monthly probability of a fire starting (0–100). Peaks at tier 6, then falls.
damage_riskint[5]Monthly probability of structural damage.
disease_riskint[5]Monthly probability of a disease outbreak.
malaria_riskint[5]Monthly probability of malaria — higher near floodplains.
crime_riskint[5]Monthly probability of criminal activity spawning.
food_consumption_percentageint[5]Fraction of max population worth of food consumed per month (%).
food_storage_multiplierint[5]How many months of food stock the house holds.
devolve_delayint[5]Months of consecutive failed requirements before devolution applies.
unreachable_ticks_devolve_thresholdint[5]Ticks with no road-walker access before forced devolution.
unreachable_ticks_block_evolve_thresholdint[5]Ticks with no road-walker access that block evolution.
days_without_food_devolve_thresholdint[5]Days without food delivery before devolution triggers.

Outer Field Reference

FieldDescription
typeBuilding type enum name. Must match exactly the enum value in building_type.h.
animationsSprite sheet references for each view state.
variantsAnimation variant sets for single and merged states.
building_sizeBase footprint (always 1 for housing; size grows via merge/expand at runtime).
can_mergetrue for tiers 1–14 (those that merge into 2×2). false for manors and estates, which only expand via expand_to_*().
desirability.valueBase contribution to neighbouring tiles' desirability. Negative for low tiers, positive for high tiers.
desirability.stepHow much the contribution changes per tile of distance.
desirability.step_sizeApply the step every N tiles of distance.
desirability.rangeMaximum radius of influence in tiles.
crime.baseBase crime rate added to the neighbourhood crime overlay.