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
-
- Composition & Dependency
-
- Value Ledger
-
- Item Store
-
- Shop Service
-
- Crafting Service
-
- Reward Service
-
- Idempotency
-
- Bootstrap
-
- Models & Results
-
- Telemetry
-
- 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