Simulator integration
The simulator adapter (crates/adapters/simulator) is the in-repo surface
through which scenarios drive state: accounts, balances, commitments,
transactions, and snapshots/rollback.
The contract
scenario-core defines provider-neutral service contracts
(SimulatorService and friends) in crates/scenario-core/src/context.rs.
The adapter implements them. Scenarios, flows, and packs depend only on the
contracts — never on a concrete simulator — so the backing system can be
swapped without touching scenario code.
The two implementations
The crate ships two SimulatorService implementations, kept deliberately
apart so neither borrows the other’s credibility.
RealSimulator — the real integration path
RealSimulator translates scenario operations onto the public lifecycle API
of the actual crucible-simulator flow engine, compiled from the simulator
repository at a pinned revision (the same pinning discipline
prover-adapter uses for crucible-prover):
- validation, commitment bookkeeping, nullifier accounting, transaction sequencing, and state-root computation all happen inside the simulator crates — this repository re-implements none of it (spec §3);
- reports carry the engine’s real state root, real event codes, and real error codes; a refused operation is reported as a rejection carrying the engine’s own stable code, never raised as a harness error;
- the observation channel is a closed public vocabulary —
state.root,state.version, table counts, and the operation family. The adapter never reads the simulator’s private ledger, so confidential balances and amounts cannot cross it; - the transfer flow runs through the simulator’s own deterministic
MockProofProvider. What is real is the pipeline and the state engine, not the cryptography underneath; swapping in theprover-adapterUltraHonk provider is a provider change, not an adapter change.
Reaching a real run is explicit. MockHarness::run_real_simulator wires the
real engine with the fixture proof double (isolating the SIMULATE seam), and
MockHarness::run_integration wires the real engine and the real
crucible-prover service — the only path where one run drives the real
simulate and prove layers together.
A real run has a smaller observation vocabulary than the double, because the
engine does not publish an invented public balance for a confidential token.
Scenarios that decode the double’s vocabulary use MockHarness::run; a real
run that needs a confidential value derives it from its own definition data,
which is what the privacy pack asserts.
The real engine also enforces semantics the double never modelled, and the adapters’ tests pin them: registration is scoped per token, so an account registered for one token cannot deposit another. That class of divergence is precisely why both paths exist.
InMemorySimulator — the posture double
InMemorySimulator is a fresh-per-run in-memory ledger over the embedded
fixture catalog:
- deterministic operations (register, deposit, merge, confidential transfer, withdraw) with acceptance rules driven by fixture postures (registered, frozen, balances, commitment status),
- state observations (
state.<op>), balance observations (balance.<actor>.<token>), ownership, events, and commitment status, - snapshots and rollback.
It is explicitly labeled a test double. Its posture vocabulary (frozen accounts, fixture-declared balances, a scripted replay guard) is fixture semantics a real engine has no reason to model, so it stays for the negative/adversarial packs. Runs over it validate that the scenario layer orchestrates correctly and that expected outcomes computed independently (balance arithmetic, ownership rules) hold — never that the real Confidential Token implementation is correct.
Boundary
This repository never implements token accounting or state-transition logic
(spec §3). The double exists only to make the orchestration layer testable
hermetically; RealSimulator exists so the same layer can also be pointed at
the engine it validates.