# `Cauldron2D.World.Rollback`
[🔗](https://github.com/jaman/cauldron/blob/v0.3.3/cauldron_2d/lib/cauldron_2d/world/rollback.ex#L1)

How two `Cauldron2D.World`s, one on each machine, play one game with rollback: each
runs the game on its own clock, applies its own players' input at once, and runs
frames again when the other's input arrives late.

Every join, leave and input is an action stamped with the frame it applies before.
A world stamps its own players' input `delay` frames ahead of the frame it is on, and
sends it to its peer once. The host also stamps every join and leave, its own and those
the other world asks for. Each message also says up to which frame the sender has sent
every action of its own (`through`) and which frame it is on.

A frame is run with the actions known for it. A player whose input for a frame is not
yet known keeps their last input, so held buttons stay held and none is pressed or let
go. When an action arrives for a frame already run, the world loads the state it kept
from before that frame and runs the frames again. The game's events from frames run
again are dropped. A world runs no further than `window` frames past its peer's
`through` (`advance?/1`).

Each world measures how far ahead of its peer it runs: its frame less the peer's last
reported frame, once for every frame it sends. Every 250 milliseconds it averages those
and sends the average. When its own average less the peer's is 1.5 frames or more, it
owes 0.3 of that many waits, at most `window`, and skips one step in every three until
it has waited them (`wait/1`). The world behind never hurries.

Each world sends the checksum (`Cauldron2D.Game.checksum/1`) of the state before every
60th frame once both worlds have confirmed it. A checksum that differs is counted in
`desyncs` and nothing else is done.

`delay/2` picks the delay from the round trip between the two worlds, and `delayed/2`
changes it while the worlds play: input stamped further ahead leaves frames with no
action, which keep the last input, and input stamped nearer comes after the actions
already sent for its frame, in the same order on both worlds.

# `action`

```elixir
@type action() :: {:join, term(), map()} | {:leave, term()} | {:input, term(), term()}
```

# `t`

```elixir
@type t() :: %{
  peer: pid(),
  monitor: reference(),
  host?: boolean(),
  delay: non_neg_integer(),
  window: pos_integer(),
  frames: %{required(non_neg_integer()) =&gt; [action()]},
  states: %{required(non_neg_integer()) =&gt; term()},
  through: non_neg_integer(),
  sent: [{non_neg_integer(), action()}],
  heard_at: integer(),
  peer_frame: non_neg_integer() | nil,
  samples: [integer()],
  next_average_at: integer(),
  own_average: number() | nil,
  peer_average: number() | nil,
  to_wait: number(),
  gap: non_neg_integer(),
  sums: %{required(non_neg_integer()) =&gt; term()},
  peer_sums: %{required(non_neg_integer()) =&gt; term()},
  summed: integer(),
  desyncs: non_neg_integer(),
  rollbacks: non_neg_integer(),
  rolled_frames: non_neg_integer()
}
```

# `advance?`

```elixir
@spec advance?(map()) :: boolean()
```

Whether `world` may run its next frame: no more than `window` frames past what its peer confirmed.

# `checked`

```elixir
@spec checked(map(), non_neg_integer(), term()) :: map()
```

`world` after the peer's checksum `sum` of the state before `frame`.

# `delay`

```elixir
@spec delay(number(), number()) :: pos_integer()
```

The delay in frames for a round trip of `round_trip_ms` between the two worlds at
`hz` frames a second: half the round trip and 8 milliseconds, in whole frames, from 2
to 3.

# `delayed`

```elixir
@spec delayed(map(), pos_integer()) :: map()
```

`world` with its own players' input stamped `delay` frames ahead from now on.

# `ended`

```elixir
@spec ended(map()) :: map()
```

`world` playing on alone: every action it holds for a frame not yet run applied now,
in frame order, and its rollback part gone.

# `flushed`

```elixir
@spec flushed(map()) :: map()
```

`world` after sending its peer the actions stamped since the last message, and its
average lead when one is due.

# `led`

```elixir
@spec led(map(), number()) :: map()
```

`world` after the peer's average lead.

# `local`

```elixir
@spec local(map(), action()) :: map()
```

`world` with `action` by one of its own players stamped and kept to send.

# `new`

```elixir
@spec new(pid(), boolean(), keyword()) :: t()
```

The rollback part of a world playing against `peer`. `host?` says whether this world
stamps joins and leaves. Options: `:delay` (frames, default 2) and `:window` (frames,
default 7).

# `received`

```elixir
@spec received(
  map(),
  [{non_neg_integer(), action()}],
  non_neg_integer(),
  non_neg_integer()
) :: map()
```

`world` after the peer's `actions`, `through` and `frame`: the actions kept, and the
frames already run with an action now known for them run again.

# `restored_state`

```elixir
@spec restored_state(module(), term()) :: term()
```

The game state `exported` stands for, as `Cauldron2D.Game.restore/1` makes it.

# `run`

```elixir
@spec run(map()) :: map()
```

`world` after running its next frame: the state kept, the frame's actions applied, one
step of the game run.

# `silent?`

```elixir
@spec silent?(map()) :: boolean()
```

Whether `world` has heard nothing from its peer for 7 seconds.

# `start`

```elixir
@spec start(map()) :: %{frame: non_neg_integer(), state: term(), frames: map()}
```

What the host sends the other world when it starts: the frame the host is on, its
state, and every action it holds from that frame on.

# `stats`

```elixir
@spec stats(map()) :: %{
  rollbacks: non_neg_integer(),
  frames: non_neg_integer(),
  desyncs: non_neg_integer()
}
```

The world's rollback counts: how many times it went back, how many frames it ran
again, and how many checksums differed from its peer's.

# `wait`

```elixir
@spec wait(map()) :: {boolean(), map()}
```

Whether `world` skips this step to let its peer catch up, and `world` with the wait
counted. It skips one step in every three while it owes a wait.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
