Skip to content

Planetary Production Tick

Purpose

Each tick (default 5 minutes), every owned, colonized planet runs a per-planet production cycle: colonists consume food, allocations produce fuel / organics / equipment / colonists, citadel-driven multipliers and habitability shape the rates, storage caps clamp the result, and overflow / starvation conditions feed back into morale and population. This is the per-planet step that makes empires economically alive — without it, a planet is just a parking spot.

Inputs

What triggers this: - Scheduled tick (tick_production job, default 5-minute cadence). - Manual admin trigger via POST /api/v1/admin/planets/{id}/tick. - On-demand recompute when allocations change (no resource credit; only rate refresh).

State read: - Planet.id, .owner_id, .specialization, .last_production. - Allocations: fuel_allocation, organics_allocation, equipment_allocation (sum ≤ 100% of colonists). - Resources on hand: fuel_ore, organics, equipment. - Population: colonists, max_colonists. - Buildings: factory_level, farm_level, mine_level, defense_level, research_level. - Citadel state: citadel level, turret/shield/orbital-platform counts (see ../FEATURES/planets/citadels.md). - Habitability: habitability_score (0–100). - Production multipliers: production_efficiency (0.0–2.0), specialization bonuses. - Siege state: under_siege flag.

Process

Tick loop

For each planet where owner_id IS NOT NULL and colonists > 0:

  1. Compute elapsed since last_production. If 0, skip.
  2. Compute production rates (per-day basis; see formulas).
  3. Scale rates by elapsed-time fraction (so a 5-minute tick yields 5/1440 of a daily rate).
  4. Compute colonist consumption (food = organics).
  5. Apply produced - consumed deltas, clamping to storage caps.
  6. Apply colonist growth (births - starvation).
  7. Update last_production = now.
  8. Emit planet.production_tick event with deltas.

The tick is idempotent against last_production — running twice in quick succession produces almost nothing the second time.

Production rate formula

Base rate is 10 units / colonist / day for each commodity, distributed by allocation:

fuel_rate     = fuel_allocation     * 10 * (1 + 0.10 * mine_level)
organics_rate = organics_allocation * 10 * (1 + 0.10 * farm_level)
equipment_rate= equipment_allocation* 10 * (1 + 0.10 * factory_level)

(fuel_allocation etc. is the count of colonists assigned, not a percentage. Sum of allocations cannot exceed colonists.)

Specialization bonus

If Planet.specialization is set, multiply the relevant rate:

Specialization Multipliers
agricultural organics ×1.5, others ×0.85
industrial equipment ×1.5, organics ×0.85
mining fuel ×1.5, organics ×0.85
research All ×1.10, but +25% colonist demand
military Defense buildings cheaper; production ×1.0

Habitability scaling

Colonist growth multiplier:

habitability_ratio = max(1, habitability_score) / 100
colonist_rate = colonists * 0.01 * habitability_ratio   # 1% per day at 100 habitability
effective_max_colonists = max_colonists * habitability_ratio

Habitability ≤ 50 produces stagnation; ≤ 25 triggers slow population decline (target spec for very harsh worlds).

Citadel-driven multipliers

Citadel buildings provide passive bonuses:

Citadel level Production bonus
0 none
1 +5% all production
2 +10%
3 +15%
4 +20%
5 +25%

Combined with specialization, the math is:

effective_rate = base_rate
               * (1 + 0.10 * relevant_building_level)
               * specialization_multiplier
               * (1 + 0.05 * citadel_level)
               * production_efficiency

production_efficiency is the catch-all admin-tunable multiplier (0.0–2.0).

Siege effects

If Planet.under_siege == true:

  • All production rates × 0.75 (SIEGE_PRODUCTION_PENALTY = 0.25).
  • Colonist growth = 0 (population stagnates).
  • Resource theft: a fraction of generated commodities is intercepted by the besieger (📐 Design-only — currently penalty applies but no transfer).

Colonist consumption

Each colonist consumes 0.5 organics / day (food). At tick scale:

food_consumed = colonists * 0.5 * (elapsed_minutes / 1440)

If organics_on_hand < food_consumed: - food_deficit = food_consumed - organics_on_hand - organics_on_hand = 0 - Apply starvation: colonists -= ceil(food_deficit * 2) (each missing unit kills 2 colonists). - Habitability score temporarily reduced (target spec).

Storage caps

Each commodity has a maximum derived from citadel level and storage building level (target spec):

cap_fuel       = base_cap * (1 + storage_level * 0.5)
cap_organics   = base_cap * (1 + storage_level * 0.5)
cap_equipment  = base_cap * (1 + storage_level * 0.5)

If production would exceed cap: - The excess is wasted (not stored, not transferred). Surface as overflow_warning event. - 📐 Design-only — overflow could spill into the orbital station's market for a fire-sale price.

Population growth

births = colonists * 0.01 * habitability_ratio * (elapsed_minutes / 1440)
deaths = starvation_deaths + siege_deaths

new_colonists = clamp(
    colonists + births - deaths,
    0,
    effective_max_colonists
)

If colonists == 0 after the tick: planet is uninhabited (allocations zero out, production halts; ownership remains).

Tick output

After all clamping and consumption:

deltas = {
    "fuel_ore":   produced_fuel,
    "organics":   produced_organics - food_consumed,
    "equipment":  produced_equipment,
    "colonists":  births - deaths
}

planet.fuel_ore  = clamp(planet.fuel_ore + deltas.fuel_ore,  0, cap_fuel)
planet.organics  = clamp(planet.organics + deltas.organics, 0, cap_organics)
planet.equipment = clamp(planet.equipment + deltas.equipment, 0, cap_equipment)
planet.colonists = clamp(planet.colonists + deltas.colonists, 0, effective_max_colonists)
planet.last_production = now

Commit; emit event.

Outputs / state changes

Per tick, per planet: - Planet.fuel_ore, .organics, .equipment, .colonists updated. - Planet.last_production updated to now. - Planet.under_siege evaluated and possibly cleared (see ../FEATURES/planets/defense.md). - Events: - planet.production_tick — per-planet, includes deltas, rates, current resources. - planet.starvation_warning — if food deficit occurred. - planet.overflow_warning — if any cap was hit. - planet.colonist_milestone — at population threshold (1k, 10k, 100k, max). - Owner notification (websocket) if any warning event fires.

Invariants

  1. colonists ≥ 0, bounded by effective_max_colonists.
  2. fuel_ore, organics, equipment ≥ 0, bounded by their respective caps.
  3. fuel_allocation + organics_allocation + equipment_allocation ≤ colonists.
  4. last_production is monotonically non-decreasing.
  5. Production rates are non-negative.
  6. Tick is idempotent given the same last_production (no double-credit).
  7. Siege flag toggles via siege resolution path only — production tick does not modify it.
  8. Starvation does not produce negative organics — overflow into deaths instead.
  9. Habitability ≤ 0 zeroes growth rate; production rates still apply.
  10. Citadel level ≤ 5 (defined cap); production multiplier capped at +25%.

Failure modes

Mode Target handling
last_production corrupt / NULL Treat as now; first tick produces nothing; subsequent ticks normal.
Allocation sum exceeds colonists (e.g., colonist death) Clamp allocations to current colonist count proportionally; preserve ratios.
Habitability ≤ 0 Growth rate forced to 0; production still applies.
Specialization missing from bonus table Default multipliers (1.0) — no bonus, no penalty.
Tick scheduler runs late (huge elapsed) Cap elapsed at 24 hours per tick to prevent runaway growth.
Concurrent admin tick + scheduled tick Row lock on planet; second waits, sees updated last_production, produces nothing.
Storage cap ≤ 0 due to misconfiguration Treat as cap of max(1, base_cap); log warning.
Planet has owner but colonists == 0 Skip — no production, no events.
Siege flag set but no besieger Production penalty still applies until siege resolution clears flag.
Specialization changed mid-tick Read fresh on tick; old rate reflects new specialization for that whole tick.

Source map

Concern Path (target)
Tick service services/gameserver/src/services/planet_production_service.py (target — currently inside planetary_service.py)
Production rate calculator services/gameserver/src/services/planetary_service.py:_calculate_production_rates
Habitability effects services/gameserver/src/services/planetary_service.py:get_habitability_effects
Specialization bonus table same file (_calculate_specialization_bonuses)
Siege evaluation services/gameserver/src/services/planetary_service.py:check_and_update_siege
Planet model services/gameserver/src/models/planet.py
Scheduler entry services/gameserver/src/scheduler/tick_planets.py (target — not yet split out)
Storage caps services/gameserver/src/models/planet.py (max_colonists, target storage cap fields)