Skip to content

0002 — Terraforming uses habitability points, not growth-rate percentages

Status

Accepted.

Context

Two competing models for what a terraforming project does to a planet were on the table:

  1. %-growth model — each level adds a fixed percentage to the planet's daily population growth rate (e.g. Level 1 = +5% growth, Level 5 = +25%, cumulative). Hostile planets are "rescued" by lifting their growth from negative to positive.
  2. Habitability-points model — each level adds a fixed amount to the planet's habitability_score (0–100). Habitability then drives population cap, growth multiplier, and morale through a single shared formula.

The %-growth model is intuitive ("terraforming makes the colony grow faster") but couples the terraforming subsystem to whatever growth formula is current. It also requires a separate "planet rescue" code path because a Barren planet's −0.1%/day baseline needs special handling to become viable.

The habitability-points model treats habitability_score as the single dimensionless quality measure for a planet. Terraforming raises it; everything downstream — max_population, growth multiplier, morale, life-support cost — derives from it via formulas owned by planetary_service.py. This avoids duplicating "what counts as a viable planet" logic across services.

The ground-truth code (services/gameserver/src/services/terraforming_service.py) implements the habitability-points model. The legacy design doc described the %-growth model.

A parallel decision was made about persistence shape:

  • Option A — separate terraforming_projects table with status (ACTIVE / PAUSED / COMPLETED), monthly progress columns, ongoing resource burn, and a foreign key to the planet. Mirrors how Citadel upgrades are modelled.
  • Option B — flat columns on planets (terraforming_active, terraforming_target, terraforming_start_time, terraforming_progress) plus per-level metadata in the existing active_events JSONB column.

Option B was implemented (migration b2c3d4e5f6a7_add_terraforming_columns_to_planets).

A third related decision: whether terraforming is a long monthly-tick process driven by an assigned engineer profession, or a fire-and-forget project with up-front resource cost and gradual auto-progress. The implemented service uses up-front cost + per-tick progression based on planet population, with no profession requirement.

Decision

Terraforming is modelled as habitability-points additions to Planet.habitability_score, not as growth-rate percentages.

The 5-level project ladder, with names and credit costs preserved from the original design, applies a fixed +habitability_boost on completion:

Level Name Boost
1 Basic Atmospheric +10
2 Climate Stabilization +15
3 Ecosystem Seeding +20
4 Biome Engineering +25
5 Full Terraformation +30

Habitability is the single quality knob: planetary_service.get_habitability_effects derives effective max-colonists, growth multiplier, and morale bonus from habitability_score / 100. Raising habitability automatically raises all three.

Persistence is flat columns on planets plus a single dict in active_events (keyed type: "terraforming") for level metadata. There is no separate terraforming_projects table.

Resource recipe is credits + organics + equipment (drawn from the planet's own stockpile, not the player's cargo). The legacy ore-based recipe is not implemented.

Consequences

Positive:

  • One quality field, one set of formulas. Anything that wants to know "how viable is this planet" reads habitability_score.
  • Terraforming work doesn't need to know about growth formulas — it just nudges the score and planetary_service recomputes.
  • No separate table to migrate, query, or join. Status fits in columns and JSONB.
  • Cancelling and refunds are simple: clear four columns, drop the active_events entry.

Negative:

  • The %-growth narrative ("Level 5 gives +25% growth") in older design docs no longer maps cleanly to the live system. Conversion between the two models requires multiplying through habitability_ratio = habitability / 100.
  • Multiple concurrent or queued terraforming projects on a single planet are not directly representable. The active_events dict holds at most one terraforming entry; starting a second project before the first completes is rejected.
  • Hard caps are applied at write time (boost capped at TERRAFORMING_MAX_HABITABILITY = 100), so above 90 the marginal value of a project drops sharply.

Neutral:

  • The Terraform Engineers profession from the legacy design has no implementation hook. It is documented as 📐 Design-only in FEATURES/planets/terraforming.md.
  • Planet-type reclassification (D_CLASS → H_CLASS at Level 5, etc.) is not implemented. The class enum on the planet stays fixed; only the score changes.
  • The cost-scaling formula from legacy PLANETARY_COLONIZATION.md (×1.25 / ×1.5 / ×2.0 above 40 / 70 / 90 habitability) is not implemented. Costs are flat per level.

References

  • FEATURES/planets/terraforming.md — live behaviour reference.
  • services/gameserver/src/services/terraforming_service.py:TERRAFORMING_LEVELS — authoritative constants.
  • services/gameserver/src/services/planetary_service.py:get_habitability_effects — downstream effects of habitability score.
  • services/gameserver/alembic/versions/b2c3d4e5f6a7_add_terraforming_columns_to_planets.py — schema migration.