Skip to content

Economy System — System Guarantees Matrix

This page defines the behavioural contract of the Economy system.

What this page is

No marketing. No implication. Just guarantees — and explicit non-guarantees.


Quick Navigation

    1. Composition & Dependency
    1. Value Ledger
    1. Item Store
    1. Shop Service
    1. Crafting Service
    1. Reward Service
    1. Idempotency
    1. Bootstrap
    1. Models & Results
    1. Telemetry
    1. Diagnostics Surface
  • Non-Guarantees
  • Final Summary

1. Composition & Dependency

Economy overall

Aspect Guarantee
Depends on Currency ✔ Yes — the Economy assembly references Currency and compiles out without it (modules_dependencies has always said so). The seams stay swappable: money through IValueLedger, items through IItemStore
Can replace money implementation ✔ Via IValueLedger
Can replace item implementation ✔ Via IItemStore
Owns ledger truth ❌ No
Owns inventory truth ❌ No
Orchestrates gameplay transactions ✔ Yes

Dependency boundary

Layer Responsibility
Currency Money truth, policy, authority, escrow, audit, batching
Economy Shop / crafting / reward sequencing and rollback orchestration
Item store Item ownership, add/remove rules, capacity

2. Value Ledger

IValueLedger

Aspect Guarantee
CanPay Side-effect free; a true is advisory, a false cancels the transaction in the built-in services
Pay / Grant ✔ Authoritative mutations
sourceId propagation ✔ Currency ops propagate it when supported
Hard atomicity across multi-line money bundles ❌ Not guaranteed
Rollback on partial money failure ✔ Best-effort
Policy participation ✔ Yes, when supported by implementation
Escrow usage ✔ When supported by implementation

Built-in CurrencyValueLedger

Aspect Guarantee
Money preflight and Pay alignment ✔ Uses same effective-debit policy computation path
Escrow-first path ✔ When escrow capability exists
Direct debit fallback ✔ When escrow is unavailable and policy does not require it
Batch event emission ✔ Uses Currency batching wrapper when available
Rollback of direct debits ✔ Best-effort via compensating credits
Telemetry reason normalization ✔ Uses canonical reasons in built-in implementation

3. Item Store

IItemStore

Aspect Guarantee
HasSpaceFor / CanRemove Side-effect free; a true is advisory, a false cancels the transaction in the built-in services (NoSpace / NotOwned)
Add / Remove ✔ Authoritative item mutations
sourceId propagation ❌ Not supported on item operations
Capacity / ownership enforcement ✔ By store implementation
Store implementation interchangeable ✔ Yes

Built-in Inventory-backed store

Aspect Guarantee
Requires Inventory package REV_INVENTORY_PRESENT
Uses named container ✔ Yes
GUID → definition resolver required for adds ✔ Yes
Missing resolver / unknown GUID ✔ Returns ResolverMissing
Missing container ✔ Returns ContainerMissing
Debug handle exposed ✔ Via IItemStoreDebug for diagnostics only
Gameplay support for debug handle ❌ Not supported

Scope note

The following sections describe the built-in Economy service implementations.
Custom implementations of the public abstractions may behave differently.


4. Shop Service

Built-in ShopService.Buy

Aspect Guarantee
Execution order ✔ Money → item costs → deliveries
Currency-only buys without store ✔ Supported
Item-based buy without store ✔ Fails with ServiceMissing
Preflight checks ✔ Money, item ownership, delivery space
Rollback on item-cost failure ✔ Refunds money; best-effort rollback of prior item removals
Rollback on delivery failure ✔ Best-effort undo of delivered items, removed item costs, and money refund
Hard atomicity ❌ Not guaranteed
Uses caller-facing abstractions only ✔ Yes (IValueLedger, IItemStore)

Built-in ShopService.Sell

Aspect Guarantee
Execution order ✔ Remove items first → grant payout
Requires store ✔ Yes
Requires payout money lines ✔ Yes in built-in implementation
Rollback on payout failure ✔ Best-effort re-add of removed items
Hard atomicity ❌ Not guaranteed

5. Crafting Service

Built-in CraftingService

Aspect Guarantee
Execution order ✔ Pay money → remove ingredients → add result
Requires ledger ✔ Yes
Requires store ✔ Yes
Requires valid result item ✔ Yes
Ingredient ownership preflight ✔ Yes
Result space preflight ✔ Yes
Rollback on ingredient removal failure ✔ Refunds money when already paid
Rollback on result-add failure ✔ Best-effort re-add of removed ingredients and money refund
Hard atomicity ❌ Not guaranteed
Telemetry Currency ops propagate sourceId; item ops do not

6. Reward Service

Built-in RewardService

Aspect Guarantee
Execution order ✔ Money first → items second
Store required for item rewards ✔ Yes
Preflight item space checks ✔ Yes in built-in implementation
Money rollback if item add fails ❌ No, by design
Hard atomicity ❌ Not guaranteed
Partial-success possibility ✔ Yes when money succeeds and item stage fails
Telemetry Currency ops propagate sourceId; item ops do not

7. Idempotency

Built-in services (ShopService / CraftingService)

Aspect Guarantee
Dedup scope Whole saga (money + items together), per owner
Buy / Sell key (owner, op, requestId)requestId passed directly
Craft key (owner, "craft", rid) — rid read from sourceId ("…|rid:…")
Replayed id Returns the first successful result as a no-op (no re-charge, no re-deliver)
Replayed id, different payload ❌ Refused with EcoOpCode.IdempotencyMismatch — not answered with the earlier result
Payload comparison Ignores line order and duplicate-line spelling, so a rebuilt basket is still a retry
Failed attempt ❌ Not cached — a retry re-runs
Opt out Omit requestId (Buy/Sell), or use a sourceId with no rid (Craft)
Cache bound Per-owner ring, capacity 32 (an evicted id simply re-runs)
Rewinding it after a load IEconomyRequestWindow.ClearForOwner / ClearAll, reached by casting the service. Nothing calls it for you — Economy has no state of its own to restore and therefore no save participant. Before 1.3.0 there was no way to do it at all
Currency-layer interaction None — the rid is neutralised before the currency leg, so a currency-level idempotency decorator never participates

Note

RewardService is grant-only and is not deduped.

A load rewinds the world; the window does not follow on its own

The window is in-memory and session-scoped. RevSaveManager.Load puts back the wallet and the containers a transaction moved, but a request id used before the load is still remembered after it — so re-issuing it returns the remembered result and applies nothing, against balances that have been restored. That is not hypothetical: a request id is usually derived from the game state that issued it, and a load rewinds exactly that state.

Clear the window as part of whatever handles your load, and each service you hold separately. Crafting has always had ClearAppliedCompletions for this and Currency gained ICurrencyIdempotencyWindow; Economy was the one of the three with no way to do it.


8. Bootstrap

EconomyBootstrap / EconomyInventoryBootstrap

Aspect Guarantee
Returns abstractions only ✔ Yes
Exposes concrete implementations ❌ No
Required for using Economy ❌ No
Convenience composition helper ✔ Yes
Currency-only build (EconomyBootstrap) ✔ Yes — store is always null
Inventory-enabled build ✔ Via separate EconomyInventoryBootstrap (when Inventory package present)
store from EconomyBootstrap ❌ Always null; use EconomyInventoryBootstrap for an item store
Public contract includes concrete types ❌ No

9. Models & Results

EcoOpResult

Aspect Guarantee
Explicit success flag ✔ Yes
Machine-readable code ✔ Yes
Optional message ✔ Yes
Implicit bool conversion ❌ Not supported
Equality support ✔ Yes
Comparer-by-code available ✔ Via EcoOpResultComparers.ByCode

EcoOpCode

Aspect Guarantee
Public operation outcome set ✔ Yes
Branch-safe for gameplay/UI ✔ Yes
Covers item + money failure categories ✔ Yes

PriceBundle, ChargeLine, ItemLine

Aspect Guarantee
Public value types ✔ Yes
Money ids normalized in ChargeLine ✔ Lowercase/trimmed on construction
Item GUID trimmed in ItemLine ✔ Yes
Construction validates values ❌ No; callers should check IsValid
Bundle clones provided lists ❌ No

LedgerPreflightMode

Aspect Guarantee
Affects CanPay semantics ✔ Yes
Changes the amount Pay debits ❌ No — the policy-adjusted amount is charged in either mode
Decides whether a built-in Buy or Craft proceeds ✔ Yes — Strict refuses a charge the policy would adjust
Reports such a refusal as PolicyBlocked, not InsufficientFunds

Strict is the default and the conservative choice: if a policy would charge something other than the price asked for — a minimum-balance floor clamping the debit, say — the transaction is refused rather than completed at a different figure. PolicyApproved lets it through at the policy's amount, which may be less than the price shown.


10. Telemetry

EcoReasons

Aspect Guarantee
Public canonical reason constants ✔ Yes
Helps prevent telemetry drift when used consistently ✔ Yes
Required for API correctness ❌ No

EcoSource

Aspect Guarantee
Canonical sourceId format builder ✔ Yes
Request prefix normalization (rid:) ✔ Yes
Delimiter sanitization ✔ Yes
Bounded part lengths ✔ Yes

Propagation rules

Aspect Guarantee
Currency operations sourceId passed when supported by underlying currency
Item operations ❌ No sourceId propagation

11. Diagnostics Surface

IItemStoreDebug

Aspect Guarantee
Public visibility ✔ Yes
Intended for diagnostics/tooling only ✔ Yes
Supported gameplay dependency ❌ No
Opaque handle contract ✔ Yes
Stable underlying handle type ❌ Not guaranteed

Non-Guarantees

Economy does not guarantee

  • ❌ Independence from Currency when using the built-in Currency-backed stack
  • ❌ Hard transactional atomicity across money + item flows
  • ❌ Perfect rollback under arbitrary underlying service failures
  • ❌ Networking / replication / prediction
  • ❌ Server authority implementation
  • sourceId propagation on item operations
  • ❌ Automatic inventory support without an IItemStore
  • ❌ Automatic escrow composition just because Currency policy requires it
  • ❌ Stable concrete built-in implementation types
  • ❌ Debug-handle stability for gameplay use

Final Summary

Layer Strong Guarantee Best Effort Not Guaranteed
Public abstractions
Built-in composition via bootstrap
Money orchestration through ledger
Item orchestration through store
Multi-step rollback
Hard atomicity across mixed resources
Money rollback in reward flows
Concrete implementation stability

System Philosophy

Economy is:

  • explicit
  • orchestrated
  • abstraction-first
  • honest about rollback limits

It is not:

  • a ledger
  • an inventory system
  • a networking layer
  • an ACID transaction engine