# Changelog

All notable changes to this project are documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] — 2026-08-20

### Added

- `c:TimeWarp.Model.commit/2` may return `{:ok, new_model_state}` in addition to
  `:ok`. Fossil collection threads the returned state back into the logical
  process, which gives a model the only point at which it can release state that a
  commit has made unreachable. `handle_event/3` never observes GVT and so cannot
  determine what is safe to drop; a model that accumulates per-event state and
  always returns `:ok` grows for the length of the run. Events are committed in
  ascending virtual-time order and the state is threaded through that fold, so
  commit order is now observable in the resulting state.

  Pruning is bounded by GVT lag rather than immediate: a rollback restores a state
  snapshot from history, and snapshots at or above GVT were taken before the prune,
  so they still reference the released terms until the same fossil-collection pass
  drops them.

  Returning `:ok` remains valid, so existing models are unaffected.

- `:lp_call_timeout` — timeout in milliseconds for the coordinator's own calls
  into logical processes, default `5_000`. These execute inside the coordinator
  process and a logical process may be remote, so exceeding the timeout fails the
  run rather than an individual call.

- `:quiescence_timeout_ms` — ceiling on the freeze-confirm quiescence poll,
  default `1_000`. The workable value scales with link latency, so a distributed
  run may require more than a local one.

- `TimeWarp.stop_run/1` cancels a run, tearing down its logical processes and
  returning the engine to idle. The function existed on the coordinator and the
  public documentation directed callers into that internal module; it is now part
  of the public surface.

  It waits without a timeout. Teardown reaches into every placement node's
  supervisor, which can exceed the default five-second call timeout on a
  multi-node run; a caller that timed out would exit while the teardown continued
  and completed, reporting a crash from a call that had in fact worked.

### Changed

- `TimeWarp.start_run/1` returns `{:error, :run_active}` when a run is already in
  progress, leaving the active run untouched.

- `TimeWarp.Examples.KeyedWindow` and `TimeWarp.Examples.BufferedWindow` release
  each window's accumulator and buffer on commit. Both previously retained every
  window for the length of the run.

### Fixed

- `start_run/1` against an active run matched no clause and raised
  `FunctionClauseError` inside the coordinator. This terminated the coordinator and
  every caller awaiting the *active* run, so the run already in progress was lost
  and the failure surfaced as a crash in the second.

- Three coordinator calls into logical processes — the per-process dump behind
  `report/1`, the GVT query, and the freeze-confirm poll — used the default
  five-second call timeout while executing inside the coordinator process. A remote
  or slow logical process therefore terminated the coordinator instead of failing a
  single call. These now honour `:lp_call_timeout`.

- The test suite aborted before ExUnit started when distribution was unavailable,
  reporting zero tests run. Tests requiring distribution are tagged `:distributed`
  and excluded when `Node.start/2` fails.

### Documentation

- `:placement`, `:gvt_mode` and `:net_delay` are documented in the `TimeWarp`
  moduledoc. All three were read by the coordinator and absent from the option
  list. `:net_delay` is marked test-only.

- The one-run-per-BEAM constraint is stated explicitly. The coordinator is a
  global singleton, and `start_run/1` tears down every logical process on the
  placement nodes without scoping the teardown to a single run, so concurrent runs
  destroy one another.

- `:time_window` documents that it is the only bound on retained state. Fossil
  collection reclaims only below GVT, so a workload holding GVT near the floor
  grows retained state until the VM exhausts memory, and `:none` places no limit
  on how far a logical process runs ahead.

- The `TimeWarp` moduledoc example referenced a model that does not exist and
  would not compile, and set `time_window: :none` — the value documented
  immediately below it as unbounded. It now uses `TimeWarp.Examples.PHOLD` with a
  binding window.

- `TimeWarp.Coordinator`'s moduledoc described the superseded stop-the-world
  computation as current and Mattern's algorithm as future work, though
  `:gvt_mode` defaults to `:mattern`. It now leads with Mattern's algorithm as the
  computation in use, and presents the stop-the-world variant as the test oracle
  and termination confirmation it actually is.

- References to an unpublished specification (`§N`, `Mitigation N`, `Phase-N`)
  are resolved to their meaning throughout the published modules.

## [0.1.0] — 2026-08-04

Initial release. Jefferson's Time Warp with Mattern's distributed Global Virtual
Time, mapped onto BEAM primitives: speculative execution with automatic rollback,
anti-message annihilation, fossil collection on GVT advance, an optional deadlock-free
time window, lazy cancellation, a purity divergence detector, and static multi-node
LP placement.

[0.2.0]: https://github.com/thatsme/timewarp/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/thatsme/timewarp/releases/tag/v0.1.0
