Skip to content

Resource Calculation Model

How the game calculates resource production, consumption, and holdings' resource states over time.


Goal

Define a calculation model that is accurate, performant, and capable of handling production chains with multiple tiers of resource dependency — without simulating every second of game time.


Core Problem

An MMO holding can be left untouched for hours or days. Simulating resource production every second for every holding in the game is computationally wasteful and unnecessary. At the same time, the game must be able to produce a correct and consistent resource state at any point when it is needed.

The model must answer: given everything that was true at a known past moment, what is true right now?


Working Directions

Event-Driven Snapshots

The game does not simulate resource changes continuously. Instead, it calculates resource state on demand at specific moments called snapshots.

A snapshot is triggered by an event that requires the current resource state to be known. Examples:

  • a player upgrades a sector (the new production rate must start from the correct baseline)
  • a resource shipment arrives or departs (the pool changes)
  • a combat event resolves against the holding
  • any configuration change that affects production or consumption rates

When a player views their holding's resource summary, no snapshot is created. The current amount is projected on the fly using last_snapshot_amount + (production_rate × elapsed_time). This is a read-only calculation — no state changes and no server-side recalculation is required.

At each snapshot, the game resolves the entire period between the previous snapshot and now using the rates that were in effect during that period. The result becomes the new baseline for future calculations.


The Snapshot Calculation

At snapshot time, for each resource type at a holding, the calculation is:

current_amount = last_snapshot_amount
              + (production_rate × elapsed_time)
              - (consumption_amount_resolved_for_period)

Production is straightforward: if a sector produces 1000 units per hour and 1.5 hours have passed, it contributed 1500 units to the pool.

Consumption requires more care — see the Consumption Resolution section below.

The result is the new last_snapshot_amount. The timestamp of the snapshot becomes the new baseline.


Consumption Resolution

Consuming sectors draw from the same resource pool that local production fills. Consumption resolution answers: for how much of the elapsed period was each consuming sector actually running?

If supply was sufficient for the full period, the consuming sector ran fully and consumed consumption_rate × elapsed_time.

If supply was insufficient, the consuming sector ran for only a fraction of the period. That fraction is:

run_fraction = available_input ÷ (consumption_rate × elapsed_time)

The consuming sector then produced only:

output = output_rate × elapsed_time × run_fraction

Because supply is not monitored continuously between snapshots, the exact moment the resource ran out is not tracked. The fraction represents the best available approximation: the sector ran at full capacity for that proportion of the period, then stopped.

Example:

A refinery sector consumes 500 units of raw ore per hour and produces 200 units of alloy per hour. The holding had 300 units of raw ore available at the last snapshot. 1 hour has elapsed. No additional ore arrived during that period.

  • Run fraction: 300 ÷ (500 × 1) = 0.6
  • Ore consumed: 500 × 1 × 0.6 = 300 units (pool reaches zero)
  • Alloy produced: 200 × 1 × 0.6 = 120 units

Multiple Consumers on the Same Resource

When multiple sectors at the same holding consume the same resource type and the combined demand exceeds available supply, the shortage is distributed proportionally based on each sector's consumption rate.

Each sector receives a share of the available resource proportional to what it would have consumed under full supply:

sector_share = sector_consumption_rate ÷ total_consumption_rate
sector_input  = available_input × sector_share
run_fraction  = sector_input ÷ (sector_consumption_rate × elapsed_time)

No sector takes priority over another by default. All degrade together at the same fraction.

Example:

Two sectors consume from the same pool. 400 units of resource are available. 1 hour has elapsed.

Sector Consumption rate Share Input received Run fraction
Refinery 500 units/hr 62.5% 250 units 0.5
Chemical Processor 300 units/hr 37.5% 150 units 0.5

Both run at 50% capacity for the period.


Rate Changes Mid-Period

When a snapshot is triggered by an event that changes a production or consumption rate — such as a sector upgrade completing — the snapshot must be taken before the rate change takes effect.

The sequence is:

  1. Trigger event fires (e.g. sector upgrade completes)
  2. Snapshot is calculated using the old rates up to the current moment
  3. The new rates take effect from this moment forward
  4. A new baseline is recorded

This ensures no overlap or gap in the calculation. Every unit of time is covered by exactly one set of rates.


Cascading Shortages Across Tiers

A Tier 3 sector consumes Tier 2 processed resources. If the Tier 2 sector that supplies it ran at reduced capacity during a period — because its own Tier 1 raw input was short — the Tier 3 input pool will contain less than expected.

This cascade is resolved naturally at snapshot time: the Tier 2 output feeds the Tier 3 input pool, and when the Tier 3 snapshot is calculated, it finds less input available than the full-capacity expectation. The Tier 3 sector then runs at the proportionally reduced fraction.

All tiers are resolved within the same snapshot event. There is no separate pass per tier.


Holdings Do Not Produce Passively in Isolation

A holding does not "run" in the background generating resources that accumulate in some live counter. Nothing happens between snapshots. A holding that has had no events for 72 hours has an unchanged last_snapshot_amount and an unchanged last_snapshot_time. When the next snapshot is triggered, the 72-hour production is calculated at that moment.

This means a holding that has been left alone for a long time is not at a disadvantage — its production is fully accounted for at the next snapshot, exactly as if it had been calculated hour by hour.


Open Questions

  • Is there a maximum elapsed time over which a snapshot can calculate in a single pass, or is the period always calculated from the last snapshot regardless of duration?
  • If a holding has no events for an extended period, what eventually triggers a snapshot — is there a background housekeeping event, or does the holding only resolve when something happens to it?
  • How are resource amounts stored — as integers or as fixed-point decimals? Fractional production over sub-hour periods will produce non-integer intermediate values.
  • How are storage limits modeled? If a holding's resource pool reaches a cap, does production above the cap overflow and is lost, or does production halt?