> ## Documentation Index
> Fetch the complete documentation index at: https://hedgeem-api.qeetoto.com/llms.txt
> Use this file to discover all available pages before exploring further.

# F045 — Deal Animation (Hole Cards Fly from Shuffler)

> On deal, hole cards animate from the card shuffler/shoe to their target hand panel positions

# F045 — Deal Animation

| Field  | Value               |
| ------ | ------------------- |
| ID     | F045                |
| Phase  | 3 — UI              |
| Jira   | HEDGE-173           |
| Status | TS ❌ · JS ⬜ · UMA ✅ |

## Business Rule

When a deal is triggered (HOLE stage), each hole card **flies out from the card shuffler** sprite in the top-right corner of the game canvas and travels to its assigned position within the relevant hand panel. Cards are launched in sequence with staggered delays so each one is visually distinct. The animation gives the player a clear sense of cards being physically dealt from a shoe.

Community cards are **not** dealt with this animation — they appear via a flip reveal in place.

## Technical Design

### Card Shuffler (Shoe) Sprite

The card shuffler is a three-layer composite:

| Layer        | Atlas frame             | Position (landscape) |
| ------------ | ----------------------- | -------------------- |
| Shoe body    | `dealer_shoe_body.png`  | `(1181, 49)`         |
| Shoe glass   | `dealer_shoe_glass.png` | `(1181, 49)`         |
| Card in shoe | `back.png` (card back)  | `(1118, 79)`         |

The card-in-shoe sprite sits at `(1118, 79)` with:

* `angle: -59°`
* `skewY: -0.3`
* `scale: (0.58, 0.6)`

### Animation Sequence

Each hole card goes through **two phases**:

#### Phase 1 — Quick Launch from Shoe (\~100 ms)

The card group snaps from the shoe position toward a mid-air launch point near `(1090, 150)` while the angle sweeps from `-59°` → `-40°`. This phase is linear and brief, simulating the card being pushed out.

#### Phase 2 — Main Flight to Target (\~300–500 ms, path-dependent)

Five parallel tweens run simultaneously:

| Tween            | Property                          | Easing           |
| ---------------- | --------------------------------- | ---------------- |
| Shadow           | `shadowX`, `shadowY`, `skewY → 0` | `Sinusoidal.Out` |
| Card skew        | `skewY → 0`                       | `Sinusoidal.Out` |
| Position + angle | `x`, `y`, `angle → target`        | `Sinusoidal.Out` |
| Scale            | `scaleX`, `scaleY → target`       | `Sinusoidal.Out` |
| Group reset      | container `x`, `y`, `angle → 0`   | `Sinusoidal.Out` |

Flight duration is **dynamically calculated** from the Euclidean distance between the launch point and the card's final position:

```
pathX = 1100 − containerX − targetX
pathY = containerY + targetY − 150
path  = √(pathX² + pathY²)
flyTime = path / (2 / PLAY_SPEED)          // desktop
flyTime = path / (4 / PLAY_SPEED)          // device/mobile
```

Typical flight time: **300–500 ms** at normal speed (`PLAY_SPEED = 1`).

### Stagger Delays

Cards are launched in order with a stagger interval based on their index:

```
// For hole cards (id < 5):
delay = (id + 2 × HANDS_NUMBER + 1) × CARD_FLY_STEP × PLAY_SPEED

// For community cards (id ≥ 5):
delay = (id − 3) × CARD_FLY_STEP × PLAY_SPEED
```

`CARD_FLY_STEP = 300 ms` — interval between successive card launches.

### Timing Constants

| Constant             | Value                   | Purpose                                        |
| -------------------- | ----------------------- | ---------------------------------------------- |
| `CARD_FLY_STEP`      | 300 ms                  | Delay between successive card launches         |
| `CARD_FLIP_TIME`     | 150 ms                  | Half-duration of flip reveal (community cards) |
| `CARD_SHOW_WIN_TIME` | 350 ms                  | Win highlight animation duration               |
| `PLAY_SPEED`         | 0.5 (fast) / 1 (normal) | Global speed multiplier                        |

### V6 Implementation Notes

V6 should replicate this using Phaser 3's `this.tweens.add()`. Key differences from v5 (Phaser 2):

* Use `this.tweens.add({ targets, x, y, angle, duration, ease: 'Sine.easeOut' })` instead of `game.add.tween`
* Phaser 3 has no native `skewY` — omit skew or approximate with `scaleX` oscillation
* Use `this.time.addEvent({ delay, callback })` for the stagger scheduling
* The shoe sprite should already exist in the v6 atlas (`desktop_glass_ui` or `main_game_desktop`) — confirm frame name before implementing
* Launch sequence: start from shoe card position `(1118, 79)` with `angle: -59`, tween to mid-air waypoint `(1090, 150)` at `-40°`, then continue to final hand-panel target

### V6 Canvas Coordinates

The v6 canvas is **1280 × 720** (landscape). The v5 canvas was the same resolution for desktop. Shoe position `(1181, 49)` transfers directly.

## Mathematics

**Dynamic flight duration:**

```
d = √((1100 − cx − tx)² + (cy + ty − 150)²)
t = d / 2   [ms at PLAY_SPEED=1, desktop]
```

Where `(cx, cy)` is the hand container origin and `(tx, ty)` is the card's local position within the container.

For a card in hand 1 at approximately `(200, 400)`:

```
d = √((1100 − 0 − 200)² + (0 + 400 − 150)²) = √(900² + 250²) ≈ 933 px
t ≈ 933 / 2 ≈ 467 ms
```

## Acceptance Criteria

| ID         | Criterion                                                                                     | Playwright assertion                                         |
| ---------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| AC-F045-01 | On HOLE stage, each hole card starts at the shoe position `(~1118, 79)` before animating      | `expect(cardSprite.x).toBeCloseTo(1118, 0)` at frame 0       |
| AC-F045-02 | Each hole card reaches its final target position within 800 ms of its launch                  | Assert card position equals target within timeout            |
| AC-F045-03 | Cards are launched sequentially with ≥ 250 ms stagger between each                            | Measure launch timestamps per card                           |
| AC-F045-04 | Card angle transitions from −59° at shoe to ≈ 0° (or hand target angle) at destination        | `expect(cardSprite.angle).toBeCloseTo(0, 5)` after animation |
| AC-F045-05 | Community cards do **not** use the fly animation — they flip in place                         | Assert community card x/y does not change during reveal      |
| AC-F045-06 | Animation completes correctly at both `PLAY_SPEED = 1` (normal) and `PLAY_SPEED = 0.5` (fast) | Run game in both speed modes and assert final positions      |

## Version Parity

| Version         | Status            | Notes                                                     |
| --------------- | ----------------- | --------------------------------------------------------- |
| JS (v4)         | ⬜ Not yet audited | Likely implemented — same Phaser 2 codebase as v5         |
| TypeScript (v6) | ❌ Not implemented | Cards placed statically; no fly animation                 |
| UMA (v5)        | ✅ Implemented     | Full two-phase fly with 5 parallel tweens, stagger delays |

## V5 Source References

| File                                                        | Lines   | Purpose                                                  |
| ----------------------------------------------------------- | ------- | -------------------------------------------------------- |
| `source/client_source/src/js/Game/gameTypes/cardDesktop.js` | 42–127  | `throwCard()` + `_launchCard()` — desktop deal animation |
| `source/client_source/src/js/Game/gameTypes/cardDevice.js`  | 72–135  | Mobile variant (faster fly speed: `path/4`)              |
| `source/client_source/src/js/Game/gameView.js`              | 349–365 | Shoe/shuffler sprite setup and coordinates               |
| `source/client_source/src/js/Game/gameView.js`              | 51–54   | Animation timing constants                               |
