feat(replay): playback controls — pause / resume / step + Space accelerator

Third commit on the B-2 replay screen-takeover redesign. Adds the
ability to pause an in-flight replay, step through it one move at
a time while paused, and resume — both via on-screen buttons
(UI-first contract per CLAUDE.md §3.3) and the optional `Space`
keyboard accelerator.

State shape: a new `paused: bool` field on
`ReplayPlaybackState::Playing`. The `tick_replay_playback` system
skips the `secs_to_next` decrement entirely while `paused` is set
so cursor and timer freeze together — resuming starts the next
move from a full interval. Stepping fires the next move directly
via a new `step_replay_playback` API that bypasses the tick path
and is hard-gated to `Playing { paused: true }` so it can't race
the running tick loop.

Public API additions:
- `toggle_pause_replay_playback(state)` — flips the flag, returns
  the new value (or None when not Playing).
- `step_replay_playback(state, moves_writer, draws_writer)` —
  advances exactly one move when paused; returns true on dispatch,
  false on any guard miss.

UI:
- Pause / Resume button next to Stop. Label repaints reactively
  via `update_pause_button_label`, which walks `Children` from
  the marked button to its inner `Text` so the spawn path doesn't
  need a second marker.
- Step button next to Pause. Click fires the next move; while
  unpaused the click is a no-op (guarded inside
  `step_replay_playback`).
- `Space` keyboard handler reads `Option<Res<ButtonInput>>` and
  no-ops when missing — keeps test-app compatibility under
  `MinimalPlugins`.

Test coverage: pause-button label truth table, label repaint on
state change, click-toggles-paused, step advances cursor exactly
one with paused flag preserved, step-while-running is no-op,
Space toggles paused flag. 8 new tests (1220 → 1228).

Side-effect: 25 existing `Playing { ... }` construction sites
across `replay_overlay`, `achievement_plugin`, and
`replay_playback` tests gained `paused: false` to satisfy the new
field requirement. Mechanical edit; no behavioral change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-08 15:20:45 -07:00
parent cd79877933
commit fbe48acef6
3 changed files with 435 additions and 31 deletions
@@ -1445,6 +1445,7 @@ mod tests {
replay: dummy_replay(),
cursor: 0,
secs_to_next: 0.0,
paused: false,
};
app.update();
assert!(
@@ -1480,6 +1481,7 @@ mod tests {
replay: dummy_replay(),
cursor: 0,
secs_to_next: 0.0,
paused: false,
};
app.update();
@@ -1512,6 +1514,7 @@ mod tests {
replay: dummy_replay(),
cursor: 0,
secs_to_next: 0.0,
paused: false,
};
app.update();
*app.world_mut().resource_mut::<ReplayPlaybackState>() =
@@ -1534,6 +1537,7 @@ mod tests {
replay: dummy_replay(),
cursor: 0,
secs_to_next: 0.0,
paused: false,
};
app.update();
*app.world_mut().resource_mut::<ReplayPlaybackState>() =
@@ -1559,6 +1563,7 @@ mod tests {
replay: dummy_replay(),
cursor: 0,
secs_to_next: 0.0,
paused: false,
};
app.update();
*app.world_mut().resource_mut::<ReplayPlaybackState>() =