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_permissionsotherwise); get, list, and simulate are open to any authenticated user. Updates bumpversion. - States are per-(user, resource), auto-created at
initial_valueon 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
linearto diminishing idle accrual, plus timezone-awaredaily/weeklyresets 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 tobank_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 20Plus 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-91Spend 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-overflowsource 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.
Paid offers
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 7dReading 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
- Chrono API - every endpoint, callable
- Curves, resets, and simulation - the six curve types, resets, re-anchoring gotchas, and previewing designs with the simulator
- Related: Throttle for rate limiting as a product, Job Queue for scheduled execution, Treasury for settling paid offers