import { type Curve } from "../game/progression.js"; /** * How cycles that came due between two {@link advanceLedger} calls are settled: * - `"each"` — replay every missed cycle as its own transaction (bounded by the limits below); * - `"sum"` — collapse the missed cycles into one transaction of the combined amount; * - `"skip"` — apply only the most recent cycle and discard the rest (idle income that does not bank). */ export type CatchUpPolicy = "each" | "sum" | "skip"; /** * A serializable recurring resource flow. Every field is plain JSON so the whole * {@link ResourceLedger} round-trips through `structuredClone`/`JSON`. Dynamic, context-aware * behaviour (providers, tax curves, affordability caps) lives in the runtime * {@link ResourcePolicy} pipeline, never in this data. */ export interface ScheduledRule { /** Stable id, unique within a ledger. */ id: string; /** Resource/currency key that moves. */ currency: string; /** Positive nominal amount moved per cycle before policies run. */ amount: number; /** Cadence in game-seconds; must be `> 0`. */ everySeconds: number; /** First due time in game-seconds; defaults to the ledger clock when the rule is added. */ startSeconds?: number; /** Inclusive end time; omitted means the rule never expires. */ endSeconds?: number; /** Account debited each cycle; omitted mints the amount from an external source (production). */ source?: string; /** Account credited each cycle; omitted burns the amount into an external sink (upkeep/tax). */ recipient?: string; /** Finite reserve consumed by the nominal amount each cycle; the rule depletes when it empties. */ reserve?: number; /** Catch-up settlement policy; defaults to `"each"`. */ catchUp?: CatchUpPolicy; /** Per-rule offline limit: due cycles beyond this in one advance are dropped, not banked. */ maxCatchUpCycles?: number; } /** Mutable per-rule progress, kept separate from the immutable {@link ScheduledRule} definition. */ export interface RuleCursor { /** Absolute time of the next cycle that has not yet been settled. */ nextDueSeconds: number; /** Remaining finite reserve, when the rule declared one. */ reserveRemaining?: number; /** Total cycles fired so far — a stable, monotonic cycle index. */ fired: number; /** Paused rules keep their cursor but fire nothing until resumed. */ paused: boolean; /** Set once the rule expired or fully depleted; it never fires again. */ done: boolean; } /** Fully serializable scheduled-transaction state: clock, balances, rule definitions, and cursors. */ export interface ResourceLedger { nowSeconds: number; accounts: Record>; rules: Record; cursors: Record; } /** One resource movement produced by a due cycle, before it is applied to balances. */ export interface ResourceTransaction { ruleId: string; currency: string; amount: number; source?: string; recipient?: string; /** Which cycle of the rule produced this (from {@link RuleCursor.fired}). */ cycleIndex: number; atSeconds: number; /** Ordered provenance tags appended by the policy pipeline. */ provenance: readonly string[]; } /** Read-only context handed to every {@link ResourcePolicy} for a transaction. */ export interface PolicyContext { /** The rule that scheduled this flow. */ rule: ScheduledRule; /** Current balance snapshot (reflects transactions already applied this advance). */ balanceOf(account: string, currency: string): number; /** Caller-supplied numeric context bag read by threshold/curve modifiers. */ vars: Readonly>; nowSeconds: number; } /** * A composable transform over one transaction. Return the transactions to keep: * `[]` rejects it, `[txn]` passes/transforms it, and multiple entries split it. */ export type ResourcePolicy = (txn: ResourceTransaction, ctx: PolicyContext) => readonly ResourceTransaction[]; /** Deterministic rounding applied to every transaction amount before it touches balances. */ export interface Precision { /** Quantisation step, e.g. `1` for whole units or `0.01` for cents. */ quantum: number; /** Rounding mode; defaults to `"round"`. */ mode?: "floor" | "round" | "ceil"; } /** A `{ min, factor }` band for {@link thresholdScale}; the highest band whose `min` is met wins. */ export interface ThresholdBand { min: number; factor: number; } /** Per-advance settings: the policy pipeline, caller context, rounding, and safety bounds. */ export interface AdvanceOptions { /** Policy pipeline applied to every transaction, in order. */ policies?: readonly ResourcePolicy[]; /** Numeric context exposed to policies via {@link PolicyContext.vars}. */ vars?: Readonly>; /** Rounding applied to each transaction amount before it hits balances. */ precision?: Precision; /** Global safety bound on individual cycles settled per rule per advance; defaults to `10000`. */ maxCyclesPerRule?: number; } /** The kinds of lifecycle signal {@link advanceLedger} can emit for a rule. */ export type LedgerEventKind = "started" | "fired" | "skipped" | "depleted" | "ended"; /** Lifecycle signal emitted by {@link advanceLedger}. */ export interface LedgerEvent { kind: LedgerEventKind; ruleId: string; atSeconds: number; /** Cycle count for `fired`/`skipped`, or the consumed reserve for `depleted`. */ detail: number; } /** A {@link ResourceTransaction} after policies and rounding, as actually applied to balances. */ export type AppliedTransaction = ResourceTransaction; /** The outcome of {@link advanceLedger}: the new ledger plus applied transactions and events. */ export interface AdvanceResult { ledger: ResourceLedger; applied: AppliedTransaction[]; events: LedgerEvent[]; } /** * Create an empty scheduled-transaction ledger, or hydrate one from a saved snapshot. * State is plain data: production, depletion, tax, and upkeep are all expressed as * {@link ScheduledRule}s settled deterministically by {@link advanceLedger}. * * @capability resource-ledger schedule recurring resource transactions with policy transforms */ export declare function createResourceLedger(init?: Partial): ResourceLedger; /** Read a single balance; unknown account/currency pairs read as `0`. */ export declare function balanceOf(ledger: ResourceLedger, account: string, currency: string): number; /** * Register a recurring rule and seed its cursor. Returns a new ledger; the rule's first cycle * is due at `rule.startSeconds` (defaulting to the current clock). */ export declare function addScheduledRule(ledger: ResourceLedger, rule: ScheduledRule): ResourceLedger; /** * Remove a rule and its cursor entirely (hard cancellation). * * @capability cancel-scheduled-rule remove a scheduled rule and its cursor entirely */ export declare function cancelRule(ledger: ResourceLedger, ruleId: string): ResourceLedger; /** * Pause a rule: its cursor is retained but it fires nothing until {@link resumeRule}. * * @capability pause-scheduled-rule stop a scheduled rule from firing while retaining its cursor */ export declare function pauseRule(ledger: ResourceLedger, ruleId: string): ResourceLedger; /** * Resume a paused rule. Its `nextDueSeconds` is unchanged, so the paused span counts as missed * cycles that the rule's {@link CatchUpPolicy} decides how to settle on the next advance. * * @capability resume-scheduled-rule resume a paused scheduled rule, deferring missed cycles to its catch-up policy */ export declare function resumeRule(ledger: ResourceLedger, ruleId: string): ResourceLedger; /** * Advance the ledger clock to `toSeconds`, settling every due cycle deterministically. * Rules are processed in sorted-id order; cycle counts are integer math; large deltas are * bounded by `catchUp`, `maxCatchUpCycles`, and `maxCyclesPerRule`. Every produced transaction * flows through the policy pipeline (transform, cap, reject, split, redirect, annotate) before it * is quantised and applied to balances. * * @capability advance-ledger settle due scheduled transactions through a policy pipeline */ export declare function advanceLedger(ledger: ResourceLedger, toSeconds: number, options?: AdvanceOptions): AdvanceResult; /** Reader over policy context: a `vars` key or a function of the context. */ export type PolicyRead = string | ((ctx: PolicyContext) => number); /** * Clamp a transaction's amount to at most `max` (a fixed number or a function of context). * * @capability policy-cap cap a scheduled transaction amount by context */ export declare function capAmount(max: number | ((ctx: PolicyContext) => number)): ResourcePolicy; /** * Take a fraction of the amount. With `to`, the taxed portion is split off into a second * transaction toward that recipient (a transfer/tax); without it, the fraction is simply removed. * * @capability policy-tax split off a fraction of a transaction as tax or transfer */ export declare function taxFraction(fraction: number, to?: string): ResourcePolicy; /** * Override the source and/or recipient accounts of a transaction. * * @capability policy-redirect redirect a scheduled transaction's source or recipient */ export declare function redirect(endpoints: { source?: string; recipient?: string; }): ResourcePolicy; /** * Drop a transaction when `predicate` holds — the reject arm of the pipeline. * * @capability policy-reject reject scheduled transactions by predicate */ export declare function rejectWhen(predicate: (txn: ResourceTransaction, ctx: PolicyContext) => boolean): ResourcePolicy; /** * Append a provenance tag to a transaction without changing its value. * * @capability policy-annotate tag a scheduled transaction's provenance without changing its value */ export declare function annotate(tag: string): ResourcePolicy; /** * Scale the amount by the highest {@link ThresholdBand} whose `min` the read value meets — a * generic bracket modifier (progressive tax, tiered upkeep) over caller data, with no built-in * currencies or brackets. * * @capability policy-threshold scale a transaction by a context threshold band */ export declare function thresholdScale(read: PolicyRead, bands: readonly ThresholdBand[]): ResourcePolicy; /** * Scale the amount by a {@link Curve} evaluated at the read value — a generic curve modifier * (population upkeep, depletion falloff, difficulty income) over caller-provided context. * * @capability policy-curve scale a transaction by a curve over context */ export declare function curveScale(read: PolicyRead, shape: Curve): ResourcePolicy;