# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: cli-08-anim.e2e.test.ts >> CLI-08 anim — local sprite advances ≥3 distinct frames during 1s KeyD hold (Wave 4 GREEN gate)
- Location: test/e2e/cli-08-anim.e2e.test.ts:32:1

# Error details

```
Error: Expected ≥3 distinct frame keys during 1s hold; observed 0 (samples=)

expect(received).toBeGreaterThanOrEqual(expected)

Expected: >= 3
Received:    0
```

# Page snapshot

```yaml
- generic [active] [ref=e1]:
  - generic:
    - generic [ref=e5]: Press T or Enter to chat
    - generic [ref=e7]:
      - generic [ref=e8]: Menu
      - button "Resume" [ref=e9] [cursor=pointer]
      - button "Settings" [disabled] [ref=e10]
      - button "Logout" [ref=e11] [cursor=pointer]
    - generic: uat_b
    - generic: uat_a
    - generic: uat_a
    - generic: uat_a
    - generic: uat_a
```

# Test source

```ts
  1  | // apps/client/test/e2e/cli-08-anim.e2e.test.ts
  2  | // [int->REQ-CLI-08]
  3  | //
  4  | // Plan 06.1-07 Task 1 — GREEN refinement of the 06.1-03 RED skeleton. Asserts
  5  | // the D-41 / D-42 sim-tick anim-rate model against the Wave 2/3 wire-up
  6  | // (Plan 06.1-04 fractional SpriteStateMachine + PlayerRenderer sim-tick lock).
  7  | //
  8  | // Pattern: adapts sprite-state.e2e.test.ts lines 16-46 (frame-string sample
  9  | // via __rebno.localFrame after key hold; PlayerRenderer.onSimulationTickLocal
  10 | // writes the frameKey under DEV/test mode only).
  11 | //
  12 | // Cadence model (per D6.1-06 canonical formula `image_speed = curspeed / 10`):
  13 | //   FRAMES_PER_TICK_AT_RUN = RUN_SPEED / 10 = 5 / 10 = 0.5
  14 | //   Run cycle has 6 frames (NaviRunD/R/U/L sprite frame_count, per extracted
  15 | //   sprite meta.json).
  16 | //   Over 1000 ms = 30 sim ticks × 0.5 frames/tick = 15 frame advances.
  17 | //   Across the 6-frame cycle, expect ≥ 3 distinct frame keys observed across
  18 | //   20 samples (50 ms apart). Three is the minimum that distinguishes a
  19 | //   genuinely-animating sprite from one stuck on a single frame.
  20 | //
  21 | // Frame-key regex (matches the atlas naming convention emitted by
  22 | // SpriteStateMachine.RUN_SPRITE_ID + padPhase): `00NN-NaviRun[DRUL][LR]?_NNN`.
  23 | // E.g. `0028-NaviRunR_003`, `0037-NaviRunDR_005`.
  24 | 
  25 | import {
  26 |   test,
  27 |   expect,
  28 |   loginAs,
  29 |   waitForGameReady,
  30 | } from './fixtures.js';
  31 | 
  32 | test('CLI-08 anim — local sprite advances ≥3 distinct frames during 1s KeyD hold (Wave 4 GREEN gate)', async ({
  33 |   page,
  34 |   accountA,
  35 |   inviteSuffix,
  36 | }) => {
  37 |   await loginAs(page, accountA, inviteSuffix);
  38 |   await waitForGameReady(page);
  39 | 
  40 |   // 1. Focus canvas so KeyD reaches Phaser input layer.
  41 |   await page.locator('canvas[data-game-ready="true"]').click();
  42 | 
  43 |   // 2. Hold KeyD and sample __rebno.localFrame every 50 ms for 1000 ms
  44 |   //    (20 samples). Distinct frame keys >= 3 proves sim-tick anim advances.
  45 |   await page.keyboard.down('KeyD');
  46 | 
  47 |   const samples: string[] = [];
  48 |   for (let i = 0; i < 20; i++) {
  49 |     await page.waitForTimeout(50);
  50 |     const localFrame = await page.evaluate(
  51 |       () =>
  52 |         (
  53 |           window as unknown as {
  54 |             __rebno?: { localFrame?: string };
  55 |           }
  56 |         ).__rebno?.localFrame ?? null,
  57 |     );
  58 |     if (typeof localFrame === 'string' && localFrame.length > 0) {
  59 |       samples.push(localFrame);
  60 |     }
  61 |   }
  62 |   await page.keyboard.up('KeyD');
  63 | 
  64 |   // 3. Distinct frame keys observed during the hold window.
  65 |   const unique = new Set(samples);
  66 |   expect(
  67 |     unique.size,
  68 |     `Expected ≥3 distinct frame keys during 1s hold; observed ${unique.size} (samples=${samples.join(',')})`,
> 69 |   ).toBeGreaterThanOrEqual(3);
     |     ^ Error: Expected ≥3 distinct frame keys during 1s hold; observed 0 (samples=)
  70 | 
  71 |   // 4. Each observed frame must match a NaviRun* pattern (sanity).
  72 |   for (const f of unique) {
  73 |     expect(f).toMatch(/^00\d{2}-NaviRun[DRUL][LR]?_\d{3}$/);
  74 |   }
  75 | });
  76 | 
```