Wiki / Developer Docs / Housing System / Adding a Tier

Adding a Tier

Step-by-step guide for adding a new housing tier or modifying an existing one.

Modify vs. Add

Most balance changes — thresholds, risk values, population — require only editing houses.js. No recompile is needed if the engine is run in hot-reload mode (--dev flag). The C++ layer only needs touching when you add a genuinely new tier that didn't exist before.

Change typeFiles to editRecompile needed?
Adjust a threshold, risk, or population valuehouses.js onlyNo (hot-reload)
Change goods or service requirementshouses.js onlyNo
Change animations / spriteshouses.js onlyNo
Add a new tier between two existing tiershouses.js + building_type.h + new C++ classYes
Rename a tier (display name)houses.js + localisation stringsNo

Step 1 — Add a config block in houses.js

Insert a new building_config.add({...}) call at the correct position in the file (tier order matters for evolution chain lookup). Copy the nearest existing tier as a template and adjust all values. See the Config Format page for a full field reference.

// src/scripts/houses.js

building_config.add({
  type: "BUILDING_HOUSE_MY_NEW_TIER",   // must match enum name exactly

  animations: {
    base:    { pack: 18, id: 5, offset: 42 },
    preview: { pack: 18, id: 5, offset: 42 },
    house:   { pack: 18, id: 5, offset: 42 },
    minimap: { pack: 18, id: 5, offset: 0  }
  },

  building_size: 1,
  can_merge: true,

  desirability: { value: 0, step: 1, step_size: 1, range: 2 },
  crime: { base: 5, value: 0 },

  model: {
    devolve_desirability: [-5,  0,  5, 10, 15],
    evolve_desirability:  [ 5, 10, 15, 20, 25],
    max_people:           [17, 16, 14, 12,  9],
    // ... all other required fields
  }
});

Step 2 — Add the enum value in building_type.h

Find the housing block in the enum and insert the new value in the correct tier position. The enum values for housing run sequentially — the numeric order determines the evolution chain, so placement matters.

// src/building/building_type.h

enum e_building_type {
    // ...
    BUILDING_HOUSE_ORDINARY_COTTAGE,
    BUILDING_HOUSE_MY_NEW_TIER,        // ← insert here
    BUILDING_HOUSE_MODEST_HOMESTEAD,
    // ...
};

After this change, recompile. All existing save files will still load — the engine handles missing building types gracefully — but houses that were previously at tiers above your insertion point may map to the shifted enum value. Test with a fresh scenario after adding a new tier.

Step 3 — Create the C++ class

Add a struct in building_house.h. Housing tier classes are intentionally minimal — everything interesting lives in the base class or is data-driven from config.

// src/building/building_house.h

struct building_house_my_new_tier : building_house {
    BUILDING_METAINFO(BUILDING_HOUSE_MY_NEW_TIER,
                      building_house_my_new_tier,
                      building_house)

    virtual void evolve(house_demands* demands) override;
};

Step 4 — Register with METAINFO

The BUILDING_METAINFO macro (defined in building_impl.h) does three things:

No additional registration calls are needed — the macro handles everything through static initialisation.

Step 5 — Implement evolve()

Add the implementation in building_house.cpp. The standard pattern is identical for all tiers — just change the target type names for the transitions.

// src/building/building_house.cpp

void building_house_my_new_tier::evolve(house_demands* demands) {
    if (base.house_population == 0) return;

    // Attempt merge (only relevant if can_merge == true in config)
    if (merge()) return;

    e_house_progress result = check_requirements(demands);

    if (result == HOUSE_DECAY && !has_devolve_delay()) return;

    if (result == HOUSE_EVOLVE) {
        change_to(BUILDING_HOUSE_MODEST_HOMESTEAD); // next tier up
    } else if (result == HOUSE_DECAY) {
        change_to(BUILDING_HOUSE_ORDINARY_COTTAGE); // one tier down
    }
}

If your new tier crosses a size boundary (e.g. 2×2 → 3×3), use expand_to_manor() instead of change_to() for the evolve path, and devolve_to_fancy_residence() for the decay path.

Step 6 — Update neighbouring tiers

The tiers on either side of your new tier need to point to it as their evolution target or devolution fallback:

// The tier just below yours — update its EVOLVE case:
void building_house_ordinary_cottage::evolve(house_demands* demands) {
    // ...
    if (result == HOUSE_EVOLVE)
        change_to(BUILDING_HOUSE_MY_NEW_TIER);  // was BUILDING_HOUSE_MODEST_HOMESTEAD
    // ...
}

// The tier just above yours — update its DECAY case:
void building_house_modest_homestead::evolve(house_demands* demands) {
    // ...
    if (result == HOUSE_DECAY)
        change_to(BUILDING_HOUSE_MY_NEW_TIER);  // was BUILDING_HOUSE_ORDINARY_COTTAGE
    // ...
}

Checklist

Done?Task
houses.js — new building_config.add() block with all model fields filled in
building_type.h — enum value added in the correct position
building_house.h — struct with BUILDING_METAINFO and evolve() declaration
building_house.cppevolve() implementation with correct up/down targets
Lower neighbour tier's evolve() updated to point up to new tier
Upper neighbour tier's evolve() updated to devolve down to new tier
Compiles without errors (static_assert will fail if runtime_data_t exceeds 186 bytes)
Tested in-game: houses evolve up to new tier and devolve correctly when a service is removed
Player-facing wiki table in docs/wiki/player/buildings/housing.html updated with the new row