import { type Curve } from "../game/progression.js"; import type { Standing, StandingRequirement } from "./standing.js"; /** * One upgrade node. Its price is the shared progression {@link Curve} evaluated at the rank being * bought, so a game authors one curve and every rank of every node follows it. */ export interface UpgradeNodeDef { id: string; label?: string; /** Cost of rank `n`, priced by this curve at `n` (1-based). */ cost: Curve; /** Ranks purchasable (default `1`). */ maxRank?: number; /** Node ids that must be owned before this one can be bought. */ requires?: readonly string[]; standing?: StandingRequirement; } /** Plain JSON state: node id → ranks owned. Nodes at rank `0` are absent. */ export interface UpgradeState { ranks: Readonly>; } /** Why a purchase was refused; the tree is left untouched on any rejection. */ export type UpgradeRejection = "unknown-node" | "max-rank" | "missing-prerequisites" | "standing" | "insufficient-funds"; /** Result of {@link UpgradeTree.buy}: the rank reached and what it cost, or why it was refused. */ export type UpgradeBuy = { ok: true; nodeId: string; rank: number; cost: number; } | { ok: false; reason: UpgradeRejection; }; /** Authored upgrade nodes with a per-node cost curve, prerequisites, a standing gate and one snapshot. */ export interface UpgradeTree { nodes(): readonly UpgradeNodeDef[]; node(nodeId: string): UpgradeNodeDef | null; /** Ranks owned for `nodeId`; `0` when unknown or unowned. */ rank(nodeId: string): number; maxRank(nodeId: string): number; /** Price of `rank` (default: the next rank), or `null` when unknown or past the cap. */ costOf(nodeId: string, rank?: number): number | null; /** What buying the next rank needs, without changing anything. */ canBuy(nodeId: string, actorId: string): UpgradeBuy; /** Buy the next rank, charging `pay` only after every gate passes. */ buy(nodeId: string, actorId: string, pay: (cost: number) => boolean): UpgradeBuy; owned(): readonly string[]; spendTotal(): number; snapshot(): UpgradeState; restore(next: UpgradeState): void; } /** Authored nodes plus the standing a node's requirement reads. */ export interface UpgradeTreeConfig { nodes: readonly UpgradeNodeDef[]; /** The standing a node's requirement reads, for both validation and purchase. */ standing?: Pick; /** Rehydrate from a prior {@link UpgradeTree.snapshot}. */ state?: UpgradeState; } /** Check authored upgrade nodes; throws `RangeError` naming the offending node or field. */ export declare function assertUpgrades(config: UpgradeTreeConfig): void; /** * An upgrade tree whose prices come from the shared curve primitive: each node authors one cost * curve, each rank is that curve at the rank, and a purchase passes prerequisites, a standing gate * and the caller's own payment step. State is one rank map that JSON round-trips, so a career save * stores what a player paid for rather than the arithmetic behind it. * * @capability upgrade-tree ranked upgrades priced by a cost curve, gated by prerequisites and standing */ export declare function createUpgradeTree(config: UpgradeTreeConfig): UpgradeTree;