Skip to content

Crafting — System Guarantees Matrix

The behavioural contract

This page defines the behavioural contract of the Crafting system.

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


Quick Navigation

  • Core Service
  • Preflight
  • Accept / Enqueue
  • Jobs / Scheduling
  • Delivery
  • Refunds / Compensation
  • Escrow Path
  • Authority
  • Time / Offline Progress
  • Persistence / Restore
  • Events
  • Modifiers / Validators
  • Routing
  • Determinism / RNG
  • Public API
  • Non‑Guarantees
  • Final Summary

1. Core Service

CraftingService

Aspect Guarantee
Orchestration root ✔ Single service owns crafting truth
Job creation ✔ Service-only
Job mutation ✔ Service-only
Runtime usage Main thread
Adapter-driven ✔ Yes
Inspector required ❌ No
UI required ❌ No

2. Preflight

Aspect Guarantee
Mutates state ❌ Never
Safe for UI / tools / teachables ✔ Yes
Check order Inputs → Currency → Space
Validators run ✔ Only after core checks succeed
No-space override via validator ❌ Not guaranteed
Routing included in space check ✔ Yes
Chance-output space policy applied ✔ Yes
Same item through several outputs ✔ Budgeted cumulatively, matching what delivery attempts
Item guid casing Ignored — "Plank" and "plank" are one item, as ItemDatabase resolves them

3. Accept / Enqueue

Aspect Guarantee
Authority checked before mutation ✔ Yes
Inputs consumed immediately (default path) ✔ Yes
Currency debited immediately (default path) ✔ Yes
Job created only after accept succeeds ✔ Yes
Partial failure compensation ✔ Best-effort
Batch support ✔ Yes
Timed craft support ✔ Yes
Cross-system atomicity ❌ Not guaranteed

4. Jobs / Scheduling

Aspect Guarantee
Queued jobs supported ✔ Yes
Running jobs supported ✔ Yes
Paused jobs supported ✔ Yes
Per-station caps ✔ Yes
Global parallel cap ✔ Yes
Queue reorder support ✔ MoveQueuedFirst
Scheduler fairness Basic fairness
Deterministic scheduling across external state changes ❌ Not guaranteed

5. Delivery

Aspect Guarantee
Modifiers re-evaluated at delivery ✔ Yes
Output routing resolved at delivery ✔ Yes
Outputs added immediately on success ✔ Yes
Delivery failure possible after accept ✔ Yes
Timed jobs deliver later ✔ Yes
Delivery atomicity (default path) ❌ Not guaranteed

6. Refunds / Compensation

Aspect Guarantee
Currency refund on delivery failure ✔ Attempted
Input refund on delivery failure Optional
Input refund success Best-effort
Currency refund success Depends on adapter
Refunds outside service ❌ Not supported
Net-zero guarantee ❌ Not guaranteed

7. Escrow Path

TryCraftImmediateEscrow

Aspect Guarantee
Scope Immediate (zero-duration) crafts
Job created ❌ No
Inventory reservations required ✔ Yes
Currency holds required ✔ Yes
Routing used ❌ No
Timed crafts supported ❌ No
Refuses missing escrow seams ✔ Yes — NoAdapter, before anything is consumed
Shipped adapter support Yes. InventoryCraftingAdapter reserves inputs and outputs
Currency holds shipped ✔ Yes — CurrencyHoldCraftingAdapter, when the stack is escrow-composed
Atomicity Depends on adapter

How the shipped reservations hold

They reserve by doing: inputs are consumed and outputs added when the reservation is taken, Release puts them back, and TryCommit only finalises. That is the same model the currency escrow uses, where EscrowCurrencyService debits at hold time and treats commit as a no-op.

It is not a stylistic choice. The escrow path unwinds nothing if an output commit fails — by then the inputs are committed — so a reservation whose commit could fail would destroy them. Doing the work up front makes commit unable to fail, which is the invariant the service is written against.

The consequence worth knowing: between reserving and committing, the inputs are already gone and the outputs already present. Escrow crafts are immediate and synchronous, so nothing observes that window except code re-entered from the same call — but a currency-hold event handler is exactly such code.

A currency cost needs an escrow-composed currency service

CurrencyHoldCraftingAdapter maps the crafting hold seam onto ICurrencyEscrow, so holds work — but only where the composed currency stack actually has an escrow layer. Without one TryHold refuses and the craft declines, having consumed nothing. Recipes with no currency cost do not need it at all.


8. Authority

Aspect Guarantee
Mutation gating supported ✔ Yes
Enqueue gated ✔ Yes
Cancel / Pause / Resume gated ✔ Yes
Scheduler control gating Optional (Gate Scheduler Controls). Covers the four station-cap setters and SetMaxParallelJobs, which changes the global concurrency cap and promotes queued jobs exactly as they do
Preflight (CanCraft, CanCraftDetailed, Probe) consults authority ❌ No — authority is evaluated at Enqueue. A preflight that passes can still be refused Unauthorized; use CanMutate(owner, out reason) if you need the answer up front
Restore / offline reconciliation gated ❌ No — a load is a rewind, not a mutation. RestoreJobs clears the live job list and re-creates jobs with no authority call, so drive it only from a trusted load path
Destroyed assigned authority ⚠ Still the gate. The reference is interface-typed, so Unity's destroyed-object null never applies: HasAuthorityGate stays true and the component keeps deciding until SetAuthority or Configure(authority:) replaces or clears it. Crafting assigns rather than discovers, so the component's lifetime is the project's
Asks per operation Not promised. Enqueue(count: n) asks once for n jobs and EnqueueMany asks per job; a serverOnly workbench with no bench authority asks the service authority twice. ICraftingAuthority states that it does not enforce when callers invoke it
Read queries gated ❌ No
Multiplayer replication ❌ Not provided

9. Time / Offline Progress

Aspect Guarantee
Gameplay time abstraction ✔ ITimeProvider
Wall-clock abstraction ✔ IWallClockProvider
Offline progress optional ✔ Yes
Paused jobs tick offline ❌ Never
Queued jobs tick offline ❌ Never — only a job that was already running accrues offline time
Offline elapsed cap Optional
acceptedAtUtc captured ✔ Yes

Persistence / Restore

Aspect Guarantee
Snapshot type Active jobs
Queued jobs restorable ✔ Yes
Running jobs restorable ✔ Yes
Paused jobs restorable ✔ Yes
Inputs/currency re-consumed on restore ❌ No
Offline completion applied during restore ✔ When enabled
Idempotent completion across repeated restore ✔ Repeated loads converge — the applied-completion record clears on restore (a load rewinds the world it described) and still dedupes within a single restore, so an offline completion applies once per load into state the other participants just rewound
Applied-completion record is bounded ❌ No — it grows for the life of the service. ClearAppliedCompletions() is the only prune; editor and development builds warn once when it gets large. A size cap is deliberately not offered: evicting ids would silently re-open double delivery for exactly the completions a restore carries
Save system provided ❌ Not provided
Persistence storage (files/cloud) ❌ Not provided
Save/restore timing responsibility Developer-defined

Persistence responsibility

Crafting defines what job data means and how it resumes
your game defines when, where, and how it is persisted.


11. Events

Aspect Guarantee
Lifecycle events exposed ✔ Yes
Progress events throttled ✔ Yes
Preflight rejection event ✔ Yes
Designer UnityEvent mirrors ✔ Yes
C# event vs its designer mirror ✔ C# first, then the mirror, phase by phase
That order across enqueue modes ✔ Identical for a single batch and for separate crafts

12. Modifiers / Validators

Aspect Guarantee
Modifiers supported ✔ Yes
Validators supported ✔ Yes
Owner/parent discovery ✔ Yes
Modifiers run at accept ✔ Yes
Modifiers run at delivery ✔ Yes
Validators mutate state ❌ Never
Modifier/validator ordering contract ❌ Not guaranteed

13. Routing

Aspect Guarantee
Output router supported ✔ Yes
Routing used in preflight ✔ Yes
Routing used at delivery ✔ Yes
Router auto-discovered ❌ No
Router used by escrow ❌ No

14. Determinism / RNG

Aspect Guarantee
Deterministic RNG supported ✔ Yes, opt-in via UseDeterministicRng(seed)
Deterministic by default ❌ No — the default is UnityEngine.Random, the engine-wide stream
Seeded results repeat with same call sequence ✔ Yes
Binomial approximation deterministic ❌ No
Exact chance rolls available ✔ Yes
Identical results if call order changes ❌ Not guaranteed

Which determinism, exactly. "Deterministic" answers three different questions and the answers differ. Stated once here so nothing above has to be read hopefully:

Question Answer
Same process, same run, replayed from one seed ✔ Yes, for a flow you drive explicitly — roll, deliver, tick. ❌ No for a session running through Update, where frame timing enters the outcome
Two processes, same build, same machine ❌ Not guaranteed. Nothing reconciles wall-clock or frame-count state between them, and a crafting save carries a fresh transaction GUID
Two platforms, or two CPU architectures ❌ Not guaranteed wherever a transcendental is involved. The pure roll paths are integer and float arithmetic only, and are bit-identical across Mono/IL2CPP and x86/ARM
Deterministic at all without opting in ❌ No. The default RNG is UnityEngine.Random, the engine-wide shared stream

The first row is what a drop-rate check and a bug repro need, and it is the one covered by tests. The second and third are what a lockstep or rollback netcode needs, and this framework does not provide them — see Authority for what it does provide.


15. Public API

Aspect Guarantee
Stable supported service surface ✔ Yes
Public recipe construction seam ✔ RecipeCore.Create
Public adapter seams ✔ Yes
Internal hooks public ❌ No
Integration adapters public ❌ No

Non‑Guarantees

Crafting does not guarantee

  • True multi-operation atomicity on the default job path
  • Multiplayer replication
  • Prediction / reconciliation
  • Server authority implementation
  • Validator execution after failed space preflight
  • Escrow support for timed crafts
  • Routing support in escrow path
  • Deterministic outcomes if RNG call order changes
  • Safe mutation outside CraftingService

  • Built-in save system or storage layer

  • Automatic cross-session identity resolution (owner / recipe)
  • Automatic save/load timing or lifecycle integration

Final Summary

Layer Strong Guarantee Best Effort Not Guaranteed
Preflight purity
Single-service job ownership
Default-path compensation
Escrow immediate craft ✔*
Timed craft delivery atomicity
Multiplayer replication
Cross-system atomicity
Persistence integration

*Depends on reservation/hold adapter correctness.


System Philosophy

System philosophy

Crafting is:

  • Explicit
  • Job-driven
  • Adapter-based
  • Authority-aware
  • Honest about guarantees