TimeWarp.Model behaviour (TimeWarp v0.1.0)

Copy Markdown View Source

What a domain model implements (spec §7). The engine handles all Time Warp mechanics — rollback, anti-messages, GVT, fossil collection.

The purity contract (hard requirement, §7/§8)

handle_event/3 MUST be pure: no side effects, no wall-clock reads, no randomness outside a seeded PRNG carried inside model_state. A model that violates this produces non-reproducible, silently-wrong results.

Two engine features defend the contract (Mitigation 3):

  • TimeWarp.Rand — a seeded PRNG carried in model_state, so rollback restores the PRNG position for free with the state snapshot.
  • The Divergence Detector (check_purity: true) — exploits the fact that rollback re-executes events, and asserts a re-execution emits the identical events it did the first time. Impurity surfaces immediately instead of corrupting results.

PRNG discipline and lazy cancellation (§5.5)

Lazy cancellation collapses the rollback storm by suppressing an anti-message when re-execution re-emits a byte-identical event. Whether it pays is a property of the model, and the deciding factor is precise: does the event that a straggler interleaves consume a PRNG draw? If it does, every subsequently re-executed event draws different numbers, so its emission changes in timestamp/payload and the match fails — even when the logical decision is identical (same routing choice, different drawn travel time is enough to kill it). A purely random model (see TimeWarp.Examples.PHOLD) therefore gets a zero match rate and lazy cannot help it.

This makes PRNG placement a design lever: keep bookkeeping events (occupancy updates, state transitions with no stochastic duration) PRNG-free, and concentrate draws in as few event types as possible. Stragglers landing on PRNG-free events re-emit identically and lazy pays; the lazy_matched stat measures whether that discipline held.

Summary

Types

An event to schedule: to target, firing at virtual time at, carrying payload.

Callbacks

Called ONLY by fossil collection, once GVT has passed this event's timestamp. The single sanctioned place for externally-visible effects. Optional.

Process one event. MUST be pure. Returns the new model state and any events to schedule at strictly future virtual times (at > at_of_current_event); target may be the LP itself.

Build the initial model state for one LP from its config args.

Types

emission()

@type emission() :: {target :: lp_id(), at :: vtime(), payload :: term()}

An event to schedule: to target, firing at virtual time at, carrying payload.

lp_id()

@type lp_id() :: TimeWarp.Event.lp_id()

vtime()

@type vtime() :: TimeWarp.Event.vtime()

Callbacks

commit(event, model_state)

(optional)
@callback commit(event :: TimeWarp.Event.t(), model_state :: term()) :: :ok

Called ONLY by fossil collection, once GVT has passed this event's timestamp. The single sanctioned place for externally-visible effects. Optional.

handle_event(payload, at, model_state)

@callback handle_event(payload :: term(), at :: vtime(), model_state :: term()) ::
  {new_model_state :: term(), [emission()]}

Process one event. MUST be pure. Returns the new model state and any events to schedule at strictly future virtual times (at > at_of_current_event); target may be the LP itself.

init(args)

@callback init(args :: term()) :: model_state :: term()

Build the initial model state for one LP from its config args.