Hey Andrew,
Yes, past() is a conditional way of accessing the global clock in simulations. You can run the simulator in two modes of operation: synchronous, which generates events over time, and asynchronous, which generates only an ordered sequence of events. Too Many Bananas is asynchronous (doesn't use the clock at all), the Elevators are pretty much only interesting synchronously (to measure timing delays). The Raft model can be run in either mode.
If I may quote an article I've been writing:
Many algorithms are designed to maintain safety properties under asynchronous assumptions, making them robust to erroneous clocks and unexpected delays (typically, even a "small" race condition is not acceptable). With Runway, it's possible to check these properties asynchronously with the model checker, and still run timing-based simulations on the same model. For example, we can check that Raft always keeps committed log entries no matter how long communication steps take, then use the same model in synchronous mode to estimate leader election times on a datacenter network.
In Runway, a timer is typically modeled by storing the time when an action should be taken, then guarding a transition rule for the action with an if-statement:
rule fireTimer {
if past(timeoutAt) {
/* take action, reset timeoutAt */
}
}
When running in synchronous mode, Runway keeps a global clock for the simulation, and past() evaluates to true if the given timestamp is earlier than the simulation's clock. In asynchronous mode, however, past() always returns true. This has the effect of making all timers fireable immediately after they are scheduled, allowing unlikely schedules to be explored.