---
phase: 6
slug: client-rebuild-mvp-gate-cli-08-hard-milestone
status: draft
nyquist_compliant: false
wave_0_complete: false
created: 2026-05-09
---

# Phase 6 — Validation Strategy

> Per-phase validation contract for feedback sampling during execution.

---

## Test Infrastructure

| Property | Value |
|----------|-------|
| **Framework** | vitest (unit + integration; pnpm-workspace) + @playwright/test (E2E two-client merge gate) |
| **Config file** | `apps/client/vitest.config.ts` (Wave 0 installs); `apps/client/playwright.config.ts` (Wave 0 installs); `apps/server/vitest.config.ts` (existing — extend); `tools/asset-pipeline/vitest.config.ts` (Wave 0 installs) |
| **Quick run command** | `pnpm --filter @rebno/client test:unit` |
| **Full suite command** | `pnpm verify:phase-6` (composite — see RESEARCH §16) |
| **Estimated runtime** | ~30 s quick (unit only); ~5–8 min full (build + lints + Playwright two-client smoke against local server) |

---

## Sampling Rate

- **After every task commit:** Run `pnpm --filter @rebno/client test:unit` (or task-scoped `--filter` for protocol/server/asset-pipeline edits)
- **After every plan wave:** Run `pnpm verify:phase-6`
- **Before `/gsd-verify-work`:** Full suite must be green AND Playwright two-client smoke green against staging deploy
- **Max feedback latency:** 30 s for quick; 8 min for full

---

## Per-Task Verification Map

> Filled by gsd-planner during plan generation. Each task in each PLAN.md must map to one row here. Stages: unit / int / e2e / lint / build / manual.

| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status |
|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------|
| TBD     | TBD  | 0    | REQ-CLI-08  | T-6-01     | Two-client smoke gates merge to main | e2e | `pnpm playwright test apps/client/test/cli-08.e2e.test.ts` | ❌ W0 | ⬜ pending |

*Planner: replace this template row with one row per task across all plans.*

*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*

---

## Wave 0 Requirements

> Test scaffolding that must exist before subsequent waves run. Each must be a separate Wave-0 task in the relevant PLAN.

- [ ] `apps/client/package.json` — vitest + @playwright/test + @colyseus/sdk + phaser deps installed (workspace member added)
- [ ] `apps/client/vitest.config.ts` — JSDOM env + path aliases (@rebno/protocol, @rebno/game-logic)
- [ ] `apps/client/playwright.config.ts` — two-context project; baseURL = staging; webServer = local apps/server for offline runs
- [ ] `apps/client/test/cli-08.e2e.test.ts` — **RED-FIRST** Playwright two-client smoke (D-19): login 2 accts → join `mvp-lobby` → drive WASD → assert nameplates visible + chat round-trip + reconnect-grace resume. Written before any client implementation; gates the entire phase.
- [ ] `apps/client/src/__test__/prediction.test.ts` — stubs for D-10 threshold-gated reconciliation against `packages/game-logic` `step()`
- [ ] `apps/client/src/__test__/extrapolation.test.ts` — stubs for D-11 step()-driven remote extrapolation
- [ ] `tools/asset-pipeline/package.json` + `tools/asset-pipeline/vitest.config.ts` — standalone (NOT a workspace member, per D-13)
- [ ] `tools/asset-pipeline/test/bootstrap.test.ts` + `test/build.test.ts` — stubs for sharp BMP→PNG decode + maxrects-packer determinism
- [ ] **(Wave-6 deliverable, NOT Wave-0 prereq)** `tools/scripts/lint-asset-pipeline.mjs` — manifest schema + atlas-integrity hash check. Authored in plan **06-08 Task 1** (the lint IS the deliverable of that plan; not a Wave-0 prerequisite for it).
- [ ] **(Wave-6 deliverable, NOT Wave-0 prereq)** `tools/scripts/lint-vite-env.mjs` — `.env.staging`/`.env.prod` shape lint (required `VITE_*` keys, secret-leak guard). Authored in plan **06-08 Task 1**.
- [ ] **(Wave-7 deliverable, NOT Wave-0 prereq)** `scripts/verify-phase-6.mjs` — composite gate: protocol build + asset-pipeline build + asset-pipeline postprocess + asset-pipeline lint + vite-env lint + client unit + client build (staging) + Playwright two-client smoke + `pnpm trace:check` (Phase 6 reqs). Authored in plan **06-09 Task 1** (the script IS the deliverable; not a Wave-0 prerequisite for it).
- [ ] **(Wave-2 stub, NOT Wave-0)** `apps/server/test/protocol-v2-handshake.integ.test.ts` — verifies coordinated server-before-client deploy (D-08): old-protocol clients receive `4400 PROTOCOL_VERSION_MISMATCH`; new clients pass through. Lands in plan **06-03 Wave 2** as a TDD pair with its server-side implementation (per the server-before-client deploy ritual — the test and the protocol-v2 handshake ship in the same plan, not in Wave 0).
- [ ] **(Wave-2 stub, NOT Wave-0)** `apps/server/test/home-portal-spawn.integ.test.ts` — D-07 amendment: new Better-Auth session ignores `characters.x/y`, spawns at home portal; grace reconnect preserves position. Lands in plan **06-03 Wave 2** alongside its server-side spawn-policy implementation (TDD pair).

---


### Wave-0 Doctrine Note (revision iteration 1)

The Wave 0 list above mixes three categories of test scaffolding. To avoid confusion:

| Category | Where it lives | Examples |
|----------|----------------|----------|
| **True Wave-0 prerequisites** (RED stubs that block subsequent waves) | Plan **06-01** Wave 1 | apps/client + tools/asset-pipeline package.json, vitest.config.ts, playwright.config.ts, cli-08.e2e.test.ts (skipped), prediction.test.ts stub, extrapolation.test.ts stub, bootstrap.test.ts (todo), build.test.ts (todo) |
| **Wave-2 TDD pairs** (test + server-side impl in the same plan, per server-before-client deploy ritual) | Plan **06-03** Wave 2 | protocol-v2-handshake.integ.test.ts, home-portal-spawn.integ.test.ts |
| **Plan deliverables** (the artifact IS the output of the plan; cannot be a prerequisite for itself) | Plans **06-08** (Wave 6) and **06-09** (Wave 7) | lint-asset-pipeline.mjs, lint-vite-env.mjs, verify-phase-6.mjs |

This note resolves the WARN-1 gap from checker iteration 1 — the original Wave 0 list conflated all three categories under a single "must exist before subsequent waves run" header. The categorisation above is the operative reading.

## Manual-Only Verifications

| Behavior | Requirement | Why Manual | Test Instructions |
|----------|-------------|------------|-------------------|
| HiDPI nearest-neighbor + integer scale (no blur on retina) | REQ-CLI-06 | Pixel-correctness on real Retina hardware not reproducible in headless Chromium at varying DPI matrices | Open `staging.rebno.decidel.com` on a 4K Retina display + 1080p external + a non-integer-DPI Windows scaling (125%/150%); zoom-in screenshot of NaviStandD sprite must show crisp pixel edges, no anti-aliasing |
| Two-player MVP gate (CLI-08 milestone artifact) | REQ-CLI-08 | The "we shipped CLI-08" milestone artifact requires human capture (screen recording) per D-19; Playwright provides regression coverage but not the milestone artifact | Operator opens 2 Chrome tabs (or 2 machines) against staging, signs in as 2 distinct accounts, walks toward each other in `mvp-lobby`, exchanges chat, captures full-window video; logged in `06-HUMAN-UAT.md` |
| Asset-pipeline dev-loop ergonomics (Aseprite round-trip) | REQ-AST-01 | `.aseprite` editor is a desktop app; export-and-rebuild is a human ergonomic check, not a CI assertion | Operator edits `assets/source/sprites/aseprite/0000-NaviStandD.aseprite` in Aseprite, exports to PNG strip + JSON via Aseprite CLI, runs `pnpm asset-pipeline build`, reloads client, confirms updated sprite renders |
| Phaser DOMElement form: browser password manager + autofill works | REQ-CLI-02 | Password-manager integrations (1Password, Chrome built-in) are not headless-driveable | Operator opens login screen, confirms password manager prompts to save + autofills on next visit; record in `06-HUMAN-UAT.md` |
| Reconnect grace banner UX feels right | REQ-CLI-09 | Banner timing + visual prominence is a subjective UX call beyond "did it reconnect" | Operator force-kills WS via DevTools (`window.colyseus.connection.transport.ws.close()`), confirms banner appears within ~1 s, banner clears within 1 s of reconnect, no jank |

---

## Validation Sign-Off

- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
- [ ] Wave 0 covers all MISSING references (12 items above)
- [ ] No watch-mode flags
- [ ] Feedback latency < 30 s quick / 8 min full
- [ ] `nyquist_compliant: true` set in frontmatter
- [ ] `wave_0_complete: true` set in frontmatter once all Wave-0 tasks ship green

**Approval:** pending
