# 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: camera-follow.e2e.test.ts >> Camera follow: walking toward room edge pans the camera (D-29 mandatory)
- Location: test/e2e/camera-follow.e2e.test.ts:18:1

# Error details

```
Error: Camera did not pan: before=(0,0) after=(0,0)

expect(received).toBeGreaterThanOrEqual(expected)

Expected: >= 8
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
```

# Test source

```ts
  1  | // apps/client/test/e2e/camera-follow.e2e.test.ts
  2  | // [int->REQ-CLI-07]
  3  | //
  4  | // Plan 06-15 Task 3 — D-29 camera-follow mandatory verification (per D-35 hook).
  5  | // Asserts that walking the local player toward the room edge causes the Phaser
  6  | // camera to pan (cameraScrollX or cameraScrollY changes by at least 8px).
  7  | //
  8  | // Uses window.__rebno deterministic test hook (D-35, exposed by 06-14 Task 3
  9  | // in dev/test mode only) — NOT WebGL canvas-pixel sampling. One screenshot
  10 | // artifact per test is fine; pixel-equality is not used for pass/fail.
  11 | //
  12 | // Threshold choice: 8px is conservative for a 1.5s walk at the BNO movement
  13 | // speed derived from Phase 4 server constants. The actual delta observed in
  14 | // a passing run is appended to SUMMARY.md for posterity (plan output item f).
  15 | 
  16 | import { test, expect, loginAs, waitForGameReady } from './fixtures.js';
  17 | 
  18 | test('Camera follow: walking toward room edge pans the camera (D-29 mandatory)', async ({
  19 |   page,
  20 |   accountA,
  21 |   inviteSuffix,
  22 | }) => {
  23 |   // 1. Login and wait for game-ready.
  24 |   await loginAs(page, accountA, inviteSuffix);
  25 |   await waitForGameReady(page);
  26 | 
  27 |   // 2. Capture initial camera + player position via __rebno hook (D-35).
  28 |   const before = await page.evaluate(() => {
  29 |     const r = (window as unknown as {
  30 |       __rebno?: {
  31 |         cameraScrollX?: number;
  32 |         cameraScrollY?: number;
  33 |         localPlayerX?: number;
  34 |         localPlayerY?: number;
  35 |       };
  36 |     }).__rebno;
  37 |     return {
  38 |       scrollX: r?.cameraScrollX ?? 0,
  39 |       scrollY: r?.cameraScrollY ?? 0,
  40 |       x: r?.localPlayerX ?? 0,
  41 |       y: r?.localPlayerY ?? 0,
  42 |     };
  43 |   });
  44 | 
  45 |   // 3. Focus the canvas so key events reach the Phaser input layer.
  46 |   await page.locator('canvas[data-game-ready="true"]').click();
  47 | 
  48 |   // 4. Hold ArrowRight (or 'd') for ~1500ms — enough to cross the camera
  49 |   //    deadzone (dzW = 32px per BNO hBorder derivation in GameScene.ts).
  50 |   await page.keyboard.down('d');
  51 |   await page.waitForTimeout(1_500);
  52 |   await page.keyboard.up('d');
  53 | 
  54 |   // 5. Brief settle time for prediction/reconciliation.
  55 |   await page.waitForTimeout(200);
  56 | 
  57 |   // 6. Capture post-walk camera + player position.
  58 |   const after = await page.evaluate(() => {
  59 |     const r = (window as unknown as {
  60 |       __rebno?: {
  61 |         cameraScrollX?: number;
  62 |         cameraScrollY?: number;
  63 |         localPlayerX?: number;
  64 |         localPlayerY?: number;
  65 |       };
  66 |     }).__rebno;
  67 |     return {
  68 |       scrollX: r?.cameraScrollX ?? 0,
  69 |       scrollY: r?.cameraScrollY ?? 0,
  70 |       x: r?.localPlayerX ?? 0,
  71 |       y: r?.localPlayerY ?? 0,
  72 |     };
  73 |   });
  74 | 
  75 |   // 7. Assert camera panned at least 8px total (conservative threshold for 1.5s walk).
  76 |   const cameraDelta =
  77 |     Math.abs(after.scrollX - before.scrollX) +
  78 |     Math.abs(after.scrollY - before.scrollY);
  79 |   expect(
  80 |     cameraDelta,
  81 |     `Camera did not pan: before=(${before.scrollX},${before.scrollY}) after=(${after.scrollX},${after.scrollY})`,
> 82 |   ).toBeGreaterThanOrEqual(8);
     |     ^ Error: Camera did not pan: before=(0,0) after=(0,0)
  83 | 
  84 |   // 8. Secondary: player moved (position changed on at least one axis).
  85 |   const playerMoved = after.x !== before.x || after.y !== before.y;
  86 |   expect(
  87 |     playerMoved,
  88 |     `Player did not move: before=(${before.x},${before.y}) after=(${after.x},${after.y})`,
  89 |   ).toBe(true);
  90 | });
  91 | 
```