ServicesChrono

Chrono

Temporal resources computed lazily from a timestamp and a curve: energy bars, ability cooldowns, API-credit drips, idle accrual - with zero background jobs. Designers author a definition (cap, curve, overflow, resets); the server stores only each user's value at last interaction plus a timestamp, and materializes the current value from the server clock - clients never supply timestamps, so device-clock cheating is impossible.

When to reach for it: free-to-play energy with paid refills, server-authoritative cooldowns, API credits that drip back on an interval, idle-game offline production, daily actions with timezone-fair resets.

When not to: limiting request rates as a product feature belongs to Throttle; running code at a future time belongs to Job Queue - Chrono never executes anything, it only answers "what is this value now".

Concepts

  • Definitions are shared templates: create/update/delete are admin-only (verified: 403 insufficient_permissions otherwise); get, list, and simulate are open to any authenticated user. Updates bump version.
  • States are per-(user, resource), auto-created at initial_value on first touch. Identity comes from the JWT; you act only on your own state.
  • Lazy materialization - every read, spend, refill, and materialize evaluates the curve over elapsed time, applies resets and modifiers, and persists the result. Reads are not side-effect free.
  • Curves and resets - six curve types from linear to diminishing idle accrual, plus timezone-aware daily/weekly resets applied lazily on read (verified live: a spent-down resource returned its cap on the first read after its daily boundary, no scheduled job involved). Both are detailed on Curves, resets, and simulation.
  • The overflow bank catches regen or grants beyond the cap when the definition opts in (mode: "bank"), up to bank_cap.
  • Cooldowns are just resources - a cap-1 definition whose curve refills in the cooldown duration; there is no separate cooldown type.

Define a resource (admin)

snug chrono definitions create --file ./energy.json
snug chrono definitions list --page 1 --page-size 20

Plus get, update, and delete by --resource-id. Deleting a definition does not delete user states - recreating the same id revives them (verified). energy.json - a 10-cap bar regenerating 0.5/s:

{
  "id": "res_energy", "name": "Energy", "cap": 10, "initial_value": 5,
  "curve": { "type": "linear", "rate_per_second": 0.5 },
  "overflow": { "mode": "bank", "bank_cap": 5, "drain_order": "value_first" }
}

Read

snug chrono read --resource-id res_energy, or over HTTP GET /api/v1/chrono/resources/{resource_id}; enveloped first read, captured live:

{
  "status": 200, "msg": "OK",
  "data": {
    "resource_id": "res_energy",
    "value": 5.0, "cap": 10.0, "bank_value": 0.0, "converted_total": 0.0,
    "seconds_to_full": 10, "seconds_to_next_unit": 2, "modifiers": []
  }
}

seconds_to_full and seconds_to_next_unit drive countdown UIs, and regeneration is real: a bar at 1.0 read 4 seconds later returned 3.0.

Spend

snug chrono spend --resource-id res_energy --amount 4 --idempotency-key turn-91

Spend is a single atomic check-and-debit, so concurrent spends cannot double-spend. Insufficient funds are 409 insufficient_funds, and unusually for an error the envelope carries a data payload - captured live: { "available": 7.5, "requested": 500.0, "seconds_until_affordable": 985 }. seconds_until_affordable comes from the inverse curve - "come back in N seconds" for free. Note available: 7.5 while reads show whole numbers: rounding (round, default floor) is display-only; internal accounting stays exactly fractional. idempotency_key makes retries safe - verified, a replayed spend returned the identical result without a second debit, even after regen. A zero or negative amount is 400 invalid_amount.

Refill and the overflow bank

snug chrono refill --resource-id res_energy --amount 20 --source quest_reward --allow-overflow

source is a free-form audit tag. With --allow-overflow the excess banks up to bank_cap (verified: granting 20 on a 10-cap bar left value 10.0, bank_value 5.0, rest discarded). Without it the grant silently clips at the cap, yet the response echoes the full granted amount - do not use it to detect clipping. Spends drain per drain_order: verified with value_first, spending 12 from value 10, bank 5 left value 0, bank 3.

Cooldowns

A cooldown is a cap: 1, initial_value: 1 definition with a linear rate_per_second of 1 / cooldown_seconds. Verified live with a 4-second cooldown (rate_per_second: 0.25): spend --amount 1 returned value 0.0, the next read showed seconds_to_full: 4, and 5 seconds later the read returned 1.0. A read of 1 means ready - the spend itself is the race-free "use".

Away reports

snug chrono materialize --resource-id res_mine - same math as a read, but returns a "while you were away" report for welcome-back screens. Captured live after 5 seconds away from an idle resource accruing 2/s:

{
  "resource_id": "res_mine", "away_seconds": 5,
  "accrued_raw": 10.0, "accrued_after_diminishing": 10.0,
  "clipped_by_cap": 0.0, "banked_overflow": 0.0, "converted_overflow": 0.0,
  "final_value": 10.0, "modifiers_applied": []
}

Reports replay idempotently per absence window: a second materialize right after returned the same report (away_seconds: 5, not 0). The replay window defaults to 5 seconds.

Modifiers

snug chrono modifiers add --resource-id res_energy \
  --kind regen_multiplier --factor 2.0 --duration-seconds 86400 --source event:weekend
snug chrono modifiers remove --resource-id res_energy --modifier-id mod_tLdWHQ...

Kinds are regen_multiplier and cap_extension (idle_boost is accepted but not honored by the curve engine). All verified live: one 2x multiplier doubled a 0.5/s bar's regen; two composed to 4x per the definition's composition rule (default multiplicative); a cap_extension of 5 raised the effective cap from 10 to 15. A boost only pays out when a materialization happens while it is active: expired modifiers are pruned on the next touch and boosted seconds since the previous touch are lost - verified, a 2x boost expiring 4s into an 8s quiet window yielded exactly the unboosted amount. Read the resource before a boost expires to bank its effect. Removing an unknown id is 404 modifier_not_found.

Definitions may declare purchasable offers - full refill, cap extension, timed regen boost - and snug chrono offers purchase --resource-id res_energy --offer-id offer_full_refill --treasury-txn treasury:txn_99a1 applies the declared effect. Verified: the purchase refilled to cap and echoed the treasury_txn. The reference is recorded for audit but not verified against a live Treasury settlement - settle payment before calling this endpoint.

Enumeration and analytics

snug chrono me resources                        # all of the caller's states
snug chrono users resources --user-id some-user # self or platform admin
snug chrono analytics --resource-id res_energy --window 7d

Reading another user's states without admin is 403 insufficient_permissions (verified with a second token). Analytics requires a platform admin token and reports active_states / at_zero / at_cap / average_value for a resource.

Limits and configuration

Definition count (10000), modifiers per state (32), idempotency TTL (24h), materialize replay window (5s), and simulation size are tunable via the CHRONO_* variables in the Chrono CONFIG reference.

Reference

On this page