Files
Ferrous-Solitaire/solitaire_engine
funman300 2fb2d638bf feat(replay): floating MOVE chip above the focused card during playback
Resume-prompt Option B (smaller scope variant) — closes the
"floating MOVE chip" piece flagged as future scope in v0.21.1's
replay-overlay punch list. Leaves the multi-session screen-
takeover redesign for a future B-2.

The existing banner-anchored MOVE chip stays put — it provides
the at-a-glance overview. The new floating chip mirrors the same
text but renders above the destination pile of the most-recently-
applied move, keeping progress at the player's focal point so they
don't have to look up at the banner during fast-paced playback.

### Architecture

- New `ReplayFloatingProgressChip` marker component on a
  `Text2d` entity rendered in 2D world space. World-space
  placement (rather than UI-space + camera projection) keeps
  the math trivial — the chip uses the same `LayoutResource`
  pile coordinates that drive every other piece of pile
  geometry, so it stays correctly positioned through window
  resizes without any extra wiring.
- Lifecycle matches the banner overlay: `spawn_overlay` spawns
  the chip alongside the banner when a replay starts;
  `react_to_state_change` despawns it when the replay ends.
  The chip lives outside the UI tree (because it's world-space)
  so the despawn needs its own query — added a second
  `Query<Entity, With<ReplayFloatingProgressChip>>` parameter.
- Z = 100 keeps the chip above every card stack
  (Z_DROP_OVERLAY = 50, Z_STOCK_BADGE = 30, regular tableau
  cards stack to the low double digits at most).

### Position + visibility logic

`update_floating_progress_chip` runs each Update tick:

- Resolves the destination pile of the last-applied move
  (`replay.moves[cursor - 1]`'s `to`).
- Hides the chip when `cursor == 0` (no moves applied yet —
  nowhere meaningful to land) or when the last move was a
  `StockClick` (no destination pile, and stock-click feedback
  already lives at the stock pile — letting the chip jitter
  back to the stock every cycle would be visual noise).
- Otherwise positions the chip at `pile_position + (0,
  card_size.y * 0.6)` — half a card lifts above the pile
  centre, the extra 10 % is breathing room above the card's
  top edge so the chip doesn't visually clip.
- Updates the chip text via `format_progress(&state)` —
  shares the same MOVE N/M format with the banner chip.

### Test

New `floating_chip_spawns_and_despawns_with_overlay` pins the
lifecycle: chip absent on Inactive, exactly one chip on Playing,
absent again on return to Inactive. Position correctness needs
`LayoutResource` (which the headless fixture doesn't set up);
covered via running-game verification rather than a unit test —
the system's gate logic is small enough that pixel positioning
isn't load-bearing on a test.

1194 passing (+1 from prior 1193). Workspace clippy clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-08 13:29:38 -07:00
..