Skip to content

💰 Currency – Policies

Namespace: RevGaming.RevFramework.Currency.Policies

The Policies layer defines data-only rule assets used by the Currency system to enforce balance constraints and special requirements such as escrow-required debits.

Policies do not execute logic themselves. They are consumed by internal decorators and helpers to influence behaviour at runtime.


Purpose

  • Define per-currency constraints (minimum, maximum, clamp vs fail).
  • Allow designers to tune economic rules without touching code.
  • Act as the authoritative rule source for:
  • Caps enforcement
  • Escrow requirement enforcement
  • Transfer preview calculations
  • Keep rules lightweight, deterministic, and runtime-friendly.

Policies are passive configuration assets. All enforcement happens in decorators/helpers.


Core asset

CurrencyPolicy

A ScriptableObject that defines rule data for a currency stack.

It contains: - A list of authoring rules (CurrencyRuleAuthoring) - A fast lookup cache (rebuilt on enable/validate) - A global Require Escrow toggle - Built-in sanitisation for invalid data

Runtime code should treat policy assets as read-only configuration.

Supported public query surface: - TryGetCapRule(CurrencyId id, out CurrencyCapRule rule) - RequireEscrow


Authoring rules (CurrencyRuleAuthoring)

[Serializable]
public sealed class CurrencyRuleAuthoring
{
    public string id;        // canonical key (normalized for lookups)
    public long   min;       // minimum balance (>= 0)
    public long   max;       // maximum balance (0 = no upper bound)
    public CapMode mode;     // Clamp or Fail
}

Normalisation & sanitisation

On enable / validate:

  • Currency ids are trimmed and lowercased for lookups
  • Negative min / max values are corrected to 0
  • If min > max (and max > 0), values are swapped
  • When multiple rules share the same id, the last rule wins

At runtime, authoring rules are converted into the stable primitive:

CurrencyCapRule

This ensures gameplay code never depends on authoring shapes.


CapMode

Defines how out-of-range values are handled:

  • Clamp — silently adjust into [min, max]
  • Fail — reject the operation with a clear CurOpCode

Escrow requirement

Inside CurrencyPolicy:

public bool RequireEscrow { get; }

When RequireEscrow == true:

  • In supported factory compositions, the service is wrapped with RequireEscrowCurrencyService when this flag is enabled.
  • Debit and Transfer operations must execute against a service exposing ICurrencyEscrow
  • If escrow is missing, operations fail with CurOpCode.ServiceMissing

Important
Policies do not add escrow. They only enforce its presence.

To enable escrow behaviour, you must explicitly compose: - CurrencyFactories.WithEscrow(...), or - CurrencyFactories.WithEscrowAndPump(...)


How policies are applied

1. Caps enforcement

Applied by CappedCurrencyService using a shared utility:

CurrencyPolicyUtil.ApplyAbsolute(policy, currency, tentative, out adjusted);
  • Applies to Set / Credit / Debit directly.
  • Transfer enforcement uses transfer preview helpers that respect the same cap rules.
  • Clamps or fails based on rule mode
  • Prevents invalid balances from being written

2. Transfer previews

By default, transfer previews use the same policy rules for both source and destination currencies.

This behaviour is implemented via an ITransferPolicyProvider.

Default behaviour (same rules for both sides)

When no ITransferPolicyProvider is supplied, transfer previews use the active CurrencyPolicy directly.

In this mode:

  • The same cap rule (if present) is applied to both source and destination.
  • Preview logic internally queries CurrencyPolicy.TryGetCapRule(...).
  • No authoring or internal types are exposed to callers.

Custom providers (advanced)

For asymmetric transfer rules (e.g. different source/destination caps), you may supply your own ITransferPolicyProvider implementation.

This is an advanced extension point intended for tooling and specialised gameplay logic.


3. Escrow enforcement

When RequireEscrow == true, the RequireEscrow guard:

  • Blocks Debit / Transfer unless escrow is present
  • Does not affect Credit or SetBalance

This guarantees escrow is always used when required by design (when the guard is composed).


Usage

Applying via factories

svc = CurrencyFactories.WithCaps(svc, policy);

Bootstrap-style composition

svc = CurrencyFactories.WithCapsAuditAuthority(inner, policy, this);

Escrow enforcement is applied automatically when policy.RequireEscrow is true.


Gotchas

  • max == 0 means unbounded, not zero.
  • Missing rules mean no restriction for that currency.
  • Rule matching uses ordinal lowercase string comparison.
  • Policies are queried frequently — keep rule lists tidy.
  • CurrencyId normalises input; authoring ids should match.
  • Transfer previews apply cap rules; exact behaviour depends on the provider used.

Safe to delete

If your project does not need: - balance caps - escrow enforcement - transfer previews

You can delete this entire folder safely.

Currency can run without Policies; they are an optional configuration layer.


  • Decorators — caps and escrow enforcement wrappers
  • Core — policy utilities and transfer preview helpers
  • AbstractionsCurrencyCapRule, CapMode