---
phase: 6
slug: client-rebuild-mvp-gate-cli-08-hard-milestone
status: failed
last_updated: 2026-05-11
operator: decidel
operator_run: 2026-05-11
operator_runs:
  - date: 2026-05-10
    findings: [D-23, D-24, D-25, D-26, D-27]
    outcome: 5 findings → gap-closure plans 06-10..06-17
  - date: 2026-05-11
    findings: [D-39, D-40, D-41, D-42, D-43, D-44, D-45, D-46]
    outcome: 8 new findings after gap-closure cycle → Phase 6.1 required
---

# Phase 6 — Human UAT (CLI-08 Milestone Artifact)

> Five manual verifications driven by `06-VALIDATION.md §Manual-Only Verifications`. Test 2 (CLI-08) is THE milestone artifact for the rebuild — Capcom-derived BNO has had ~25 years between original and rebuild; this video is the proof.

[doc->REQ-CLI-01] [doc->REQ-CLI-02] [doc->REQ-CLI-06] [doc->REQ-CLI-08] [doc->REQ-CLI-09] [doc->REQ-AST-01]

> **2026-05-10 result:** ALL FIVE TESTS BLOCK CLI-08 CLOSURE. Networking + chat protocol works; UI/asset/UX layers fall short of "log in with their migrated account, walk around, and chat — bug-free" (CLAUDE.md core value). Five findings documented in `## Findings` below; per user decision (2026-05-10), all five gap-closure items are scoped to Phase 6 (no Phase 7 advance until CLI-08 truly green).

## Test 1 — HiDPI nearest-neighbor + integer scale (REQ-CLI-06)

**Why manual:** Pixel-correctness on real Retina hardware not reproducible in headless Chromium at varying DPI matrices.

**Instructions:**
1. Open `https://staging.rebno.decidel.com/?invite=$STAGING_INVITE_TOKEN` on a 4K Retina display
2. Open the same on a 1080p external monitor
3. Open the same on a Windows machine with display scaling at 125% AND at 150%
4. Log in, transition to GameScene
5. For each environment, take a 4× zoom-in screenshot of the player sprite (NaviStandD)
6. Visually inspect: pixel edges must be crisp, no anti-aliasing or sub-pixel blur

**Acceptance:**
- All sprites render with crisp pixel edges
- No fractional-scale blur on Retina or Windows 125%/150%
- Canvas snaps to integer scale on `window.resize`

**Operator Notes:** HiDPI scale check itself was not exercised because of two upstream blockers surfaced before per-DPI sweeps were possible:
- (a) Login screen text is unreadable — black title + black field labels on near-black `#0A0E1A` surface (see `uat-test-1-1.png`). Cannot read prompts in any DPI environment. **Finding #1.**
- (b) GameScene renders only blue rectangles + nameplates — no real BNO assets piped through (see `uat-test-1-2.mp4`). Pixel-correctness on rectangles is meaningless; the test is for sprites. **Finding #4.**

**Screenshots / Evidence:**
- `uat-test-1-1.png` — login screen on 1080p Chrome (Windows 125% scaling), black-on-near-black title + labels
- `uat-test-1-2.mp4` — two-window GameScene side-by-side showing blue-rectangle players (uat_a + uat_b) over empty black background

**Status:** ⬛ FAIL — blocked by Findings #1 + #4. Re-run after gap-closure plans land real assets + readable login.

---

## Test 2 — CLI-08 MILESTONE: Two-player MVP gate (REQ-CLI-08)

**Why manual:** The "we shipped CLI-08" milestone artifact requires human capture per CONTEXT D-19. Playwright provides regression coverage; this is the human deliverable.

**Instructions:**
1. Operator opens 2 Chrome browser windows (or two physical machines) against `https://staging.rebno.decidel.com/?invite=$STAGING_INVITE_TOKEN`
2. Sign in as `uat_a` in window 1 and `uat_b` in window 2 (per psyche carry-over; original doc said `alice`/`bob` — those accounts do not exist in seeded staging DB; **doc bug, fixed in this revision**)
3. Wait for both clients to enter `mvp-lobby` GameScene (`data-game-ready=true`)
4. Start a screen recording covering BOTH browser windows (OBS / Loom / built-in screen recorder)
5. In window 1 (uat_a): walk toward uat_b using WASD (~5 seconds of movement)
6. In window 2 (uat_b): walk toward uat_a
7. Confirm visually: each window shows BOTH player sprites + nameplates moving smoothly
8. In window 1 (uat_a): press Enter, type `hello uat_b`, press Enter
9. Confirm: window 2 shows `uat_a: hello uat_b` in chat HUD within 2 seconds
10. In window 2 (uat_b): press Enter, type `hi uat_a`, press Enter
11. Confirm: window 1 shows `uat_a: hi uat_a`
12. Stop the recording
13. Save the video as `.planning/phases/06-client-rebuild-mvp-gate-cli-08-hard-milestone/06-CLI-08-milestone.mp4`

**Acceptance:**
- Both clients see each other's sprite + nameplate move
- Bidirectional chat works in real time
- Video file exists and is committed (or externally linked)
- Operator types "approved — CLI-08 shipped" in the resume signal

**Operator Notes:** Networking + protocol layer works on the fresh-login path. Both clients see each other move; chat lands bidirectionally. BUT the full milestone fails on three counts:
- (a) Movement does not match BNO (no friction/accel/sub-pixel feel reproduced from extracted GML — uses arbitrary client values). **Finding #3.**
- (b) Visuals are blue rectangles + cyan nameplates only — no NaviStandD sprite, no room/tile background, no walls. **Finding #4.**
- (c) Reconnect / cookie auto-login renders only the canvas frame — chat HUD missing, game content empty. Re-entry path is broken; only first-fresh-login renders correctly. **Finding #2.**

The fresh-login two-player movement+chat slice was captured as `uat-test-1-2.mp4` (operator labeled it as Test 1 evidence; serves dual purpose). This is **NOT** the CLI-08 milestone video — that requires #2/#3/#4 fixed first.

**Video:** `uat-test-1-2.mp4` (interim — fresh-login slice only, blue rectangles, no rooms). Final `06-CLI-08-milestone.mp4` blocked on Findings #2/#3/#4.

**Status:** ⬛ FAIL — partial (networking + protocol confirmed working; presentation + reconnect path not). Re-run after Findings #2/#3/#4 close.

---

## Test 3 — Asset-pipeline Aseprite round-trip (REQ-AST-01)

**Why manual:** `.aseprite` editor is a desktop app; export-and-rebuild is a human ergonomic check, not a CI assertion.

**Instructions:**
1. Open `assets/source/sprites/aseprite/0000-NaviStandD.aseprite` in Aseprite (skip if .aseprite source files were not committed per D-15 discretion)
2. Modify a single pixel in frame 0 (any visible change)
3. Export via Aseprite CLI:
   `aseprite -b 0000-NaviStandD.aseprite --sheet ../../source/sprites/0000-NaviStandD.png --data ../../source/sprites/0000-NaviStandD.json --format json-array`
4. Run `pnpm asset-pipeline:build`
5. Run `pnpm --filter @rebno/client build:staging`
6. Reload `http://localhost:5173` (or staging) — confirm the modified pixel is visible
7. Revert the change

**Acceptance:**
- Aseprite export to PNG strip + JSON sidecar succeeds
- `asset-pipeline:build` re-packs the atlas without errors
- Client renders the modified sprite

**Operator Notes:** Cannot exercise — there are no real BNO assets in the project to edit. `assets/source/sprites/aseprite/0000-NaviStandD.aseprite` does not exist; no rooms, no tiles, no sprites are piped through the asset pipeline. The pipeline plumbing (lint:asset-pipeline + asset-pipeline:build/postprocess) was built in 06-08 but never fed real input. **Finding #4.**

**Status:** ⬛ BLOCKED — by Finding #4. Re-run once first-pass real assets land (NaviStandD + at least one room/tile set extracted from BNO source per CLAUDE.md hard rule #6: extract → document → rewrite).

---

## Test 4 — Browser password manager + autofill (REQ-CLI-02)

**Why manual:** Password manager integrations (1Password, Chrome built-in) are not headless-driveable.

**Instructions:**
1. Open `https://staging.rebno.decidel.com/?invite=$STAGING_INVITE_TOKEN` in Chrome with built-in password manager enabled (no extensions for first run)
2. Sign in with a fresh test account (not previously saved)
3. Confirm: Chrome prompts "Save password?" — accept
4. Sign out
5. Reload the page
6. Confirm: Chrome auto-fills the password field
7. Repeat with 1Password browser extension if available

**Acceptance:**
- Chrome built-in password save prompt appears
- Autofill works on subsequent visits
- (Optional) 1Password extension recognises the form
- `autocomplete="username"` and `autocomplete="current-password"` attributes verified via DevTools (UI-SPEC §Login screen)

**Operator Notes:** Cannot exercise — there is no logout affordance in the client. Once cookies are set, the LoginScene auto-bypasses to GameScene. The "Sign out" step in the instructions cannot be performed. Need a logout button (or `/logout` route + cookie/session clear) before this test is runnable. **Finding #5.**

**Status:** ⬛ BLOCKED — by Finding #5. Re-run once logout affordance exists.

---

## Test 5 — Reconnect grace banner UX (REQ-CLI-09)

**Why manual:** Banner timing + visual prominence is a subjective UX call beyond "did it reconnect".

**Instructions:**
1. Log in to staging; transition to GameScene
2. Open Chrome DevTools → Console
3. Run: `(window).colyseus?.connection?.transport?.ws?.close?.() ?? window.__rebno?.room?.connection?.close?.()`
4. Confirm: amber banner appears within 1 s with text `Reconnecting…`
5. Wait for auto-reconnect (within 10 s grace per Phase 4 D-22)
6. Confirm: banner shows `Reconnected.` (transient flash, ~1 s) then disappears
7. Confirm: player position is preserved (within-grace reconnect path)
8. Repeat: kill WS + wait > 15 s (forces past grace) → banner should show `Disconnected — click to retry` (destructive red); click → reconnect succeeds → home portal spawn (per D-07 cross-session reset)

**Acceptance:**
- Banner appears within 1 s of WS close
- Banner clears within 1 s of reconnect
- Reconnect within grace preserves position
- Reconnect past grace spawns at home portal (D-07)
- Visual transitions feel smooth (no jank)

**Operator Notes:** Networking layer reconnects (WS comes back), but post-reconnect the client renders only the canvas frame — no chat HUD, no remote players, no own player visible. Same root cause as Finding #2 (re-entry to GameScene does not re-mount HUD/renderers; init only fires on first fresh-login from LoginScene). Banner timing was not separately graded because the rendered content behind the banner is empty. **Finding #2.**

**Status:** ⬛ FAIL — by Finding #2. Re-run after re-entry/reconnect path correctly re-mounts game scene.

---

## Findings (block CLI-08 closure)

Per user decision 2026-05-10: all five findings scoped to **Phase 6** as gap-closure plans. No Phase 7 advance until CLI-08 truly green.

| # | Finding | Severity | Suggested gap-closure |
|---|---------|----------|----------------------|
| 1 | Login screen text invisible — black title + labels on `#0A0E1A` surface | high (UX blocker) | One LoginScene CSS plan: enforce UI-SPEC color tokens (foreground white/cyan); add visual regression test |
| 2 | Reconnect / cookie auto-login renders blank GameScene (canvas frame only, no HUD, no remotes, no self) | **critical (regression)** | Plan to fix re-entry init: HUD mount + renderer wiring must idempotently re-fire on every GameScene enter, not only first transition from LoginScene. Add Playwright e2e covering cookie-auto-login + WS-kill-then-reconnect rendering paths (current 06-08 e2e only covers fresh login) |
| 3 | Movement doesn't match BNO — no friction/accel/sub-pixel feel from extracted client GML | **critical (scope/identity)** | Plan to derive PredictionEngine constants from extracted Client 5-8 GML (`player_step`, `friction`, speed values, key-buffering) per CLAUDE.md hard rule #5/#6. Update game-logic + add unit tests pinning the derived values |
| 4 | No real BNO assets — blue rectangles only; no rooms, tiles, sprites, atlas content | **critical (scope/identity)** | Plan(s) to extract first-pass assets from BNO source: at minimum NaviStandD sprite atlas + one room (tiles + collision + spawn points). Pipe through existing asset-pipeline:build → atlas → Phaser texture. Tag REQ-AST-01..04 + REQ-PAR-* as appropriate |
| 5 | No logout — sticky cookie auto-login blocks Test 4 + general UX | medium | Plan to add logout button to GameScene (top-right or Esc menu); cleanly clear Better-Auth session cookie + return to LoginScene |

**Sequencing recommendation (advisory, lock in `/gsd-discuss-phase 6 --gaps` if used):**
- Wave 8: #1 (login contrast — small, no deps)
- Wave 9: #5 (logout button — small, unblocks Test 4)
- Wave 10: #2 (re-entry render fix — critical regression, must precede final UAT)
- Wave 11: #3 (movement fidelity from extracted GML — needs Phase 1 extraction artifacts)
- Wave 12: #4 (first-pass assets from BNO source — needs Phase 1 extraction artifacts; largest)
- Wave 13: re-run 06-HUMAN-UAT.md, capture final `06-CLI-08-milestone.mp4`

## Sign-Off

| Test | Requirement | Status |
|------|-------------|--------|
| 1 — HiDPI | REQ-CLI-06 | ⬛ FAIL (blocked by #1, #4) |
| 2 — CLI-08 milestone | REQ-CLI-08 | ⬛ FAIL (partial: networking ok; #2 + #3 + #4 fail) |
| 3 — Aseprite round-trip | REQ-AST-01 | ⬛ BLOCKED (#4) |
| 4 — Password manager | REQ-CLI-02 | ⬛ BLOCKED (#5) |
| 5 — Reconnect banner | REQ-CLI-09 | ⬛ FAIL (#2) |

**Operator approval:** decidel, 2026-05-10 — **NOT APPROVED.** All five tests block CLI-08 milestone closure. Five gap-closure plans required in Phase 6 before re-running UAT.

**Phase 6 status (2026-05-10):** IN_PROGRESS — UAT failed; gap-closure plans scoped (Findings #1–#5).

---

## Re-Run 2026-05-11 — after gap-closure plans 06-10..06-16 + CSP/PlayerRenderer hotfix

**Context:** Gap-closure plans 06-10..06-16 shipped (commits `691cf2c..72282e9`). Manual staging deploy required two corrections beyond the plans:
- Client bundle build (`pnpm --filter @rebno/client build:staging`) + manual atlas+manifest copy into `apps/server/public/` (CI workflow `deploy-staging.yml` handles this automatically; manual `fly deploy` skipped it).
- CSP `img-src 'self' data:` blocked Phaser's blob-loaded atlas PNG → `setFrame` crashed render loop. Hotfix commit `6dc1f81`: add `blob:` to CSP + guard `setFrame?.()` in PlayerRenderer.

After hotfix redeploy: atlas loads, real BNO sprites render, server protocol works. Seven NEW findings surface that block CLI-08 closure again.

### New findings (7) — block CLI-08 closure

| # | ID | Finding | Severity | Suggested fix area |
|---|----|---------|----------|--------------------|
| 6 | D-39 | Solid white box behind each sprite — BMP source-white not keyed to alpha during asset-pipeline bootstrap | **critical (visual identity)** | `tools/asset-pipeline/src/bootstrap.ts` — sharp pipeline must color-key `#FFFFFF` (or BNO's actual transparent-color sentinel) → alpha during BMP→PNG step. Add unit test asserting alpha channel present + zero white-edge pixels |
| 7 | D-40 | No floor tiles rendered in mvp-room — player + nametag visible over pure dark canvas, no `0023-Tile1` grid, no walls | **critical (regression)** | `apps/client/src/render/RoomRenderer.ts` — investigate why 324-tile loop emits zero sprites. Check layout signature verify (Ed25519 pubkey match?), schema dual-union resolution, atlas frame key lookup `0023-Tile1_000` |
| 8 | D-41 | Local player sprite does NOT animate during movement (frame stuck) | **high (D-26/D-31 regression)** | `apps/client/src/render/PlayerRenderer.ts::onSimulationTickLocal` + `SpriteStateMachine` — `tickAccumulator` not advancing OR `deriveFrame` returns same `frameKey` for moving state. Likely vx/vy=0 reaches the call from prediction loop |
| 9 | D-42 | Remote player sprite animates TOO FAST (faster than BNO 5fps target) | **high (D-31 regression)** | `onSimulationTickRemote` — caller is wrong tick rate. Likely called per Phaser render frame (60Hz) not per sim tick (30Hz). D-31 lock said sim-tick only |
| 10 | D-43 | Movement feels choppy — not smooth 60fps | high | After D-40/D-41/D-42 close, verify render-loop fps + prediction lerp/snap thresholds. May relate to D-31 sim-tick vs render-frame coupling |
| 11 | D-44 | Default movement = WALK speed; should be RUN speed only (single fixed speed, NO toggle) | high (gameplay identity) | `packages/game-logic/src/constants.ts` — replace `WALK_SPEED=3` (from `Create.gml curspeed=3`) with the BNO RUN speed (derive from extracted GML — legacy used Ctrl+R to set curspeed to run value; find that constant). User clarification 2026-05-11: rebno is single-speed, no Ctrl+R toggle. Drop walk entirely |
| 12 | D-45 | Nametag font too large + overlaps sprite. Legacy reference: small cyan pixel-font text positioned ABOVE sprite head with shadow (operator-supplied screenshot "Saber Mage" example) | high (visual identity) | `apps/client/src/render/Nameplate.ts` — reduce font size (target ~10-12px equivalent in canvas-coords), tighten line-height, ensure baseline-above-sprite anchor, add dark drop-shadow for legibility on light tiles. Pixel-font face (bitmap-style) preferred per legacy |
| 13 | D-46 | Camera does not follow local player — stays fixed; local sprite walks off-center toward room edge. Camera must ALWAYS keep local player centered (or near-centered within BNO view semantics) | **critical (gameplay)** | `apps/client/src/scenes/GameScene.ts` — camera `startFollow(localSprite)` + `setBounds(0, 0, roomW, roomH)` already wired by 06-14, but the follow target appears unbound (deadzone too large, or sprite ref stale after 06-15 self-heal path). Verify `cameras.main.startFollow()` is called with the actual local sprite reference AFTER local PlayerRenderer creates it, and that the deadzone shrinks to (0,0) for hard-center (or matches BNO `hBorder=304, vBorder=224, objectFollowing=0` semantics from 0058-BNCentral meta) |

### Sign-Off (2026-05-11 re-run)

| Test | Requirement | Status |
|------|-------------|--------|
| 1 — HiDPI | REQ-CLI-06 | ⬛ FAIL (blocked by D-39/D-40/D-41/D-42/D-43/D-45 — visuals broken) |
| 2 — CLI-08 milestone | REQ-CLI-08 | ⬛ FAIL (visuals + animation broken; mp4 not captured) |
| 3 — Aseprite round-trip | REQ-AST-01 | ⬛ BLOCKED (atlas alpha bug means round-trip would propagate white-box) |
| 4 — Password manager | REQ-CLI-02 | ⬜ UNTESTED (logout works per D-24 fix; not exercised in this run due to upstream visual failures) |
| 5 — Reconnect banner | REQ-CLI-09 | ⬜ UNTESTED (D-25 fix shipped; not exercised) |

**Closed by 2026-05-11 gap-closure cycle (06-10..06-16):**
- D-23 (login contrast) → closed by 06-10 ✓
- D-24 (logout/Esc menu) → closed by 06-11 ✓ (untested in this run; verified by 06-11 unit + e2e)
- D-25 (cookie blank GameScene) → closed by 06-15 ✓ (untested in this run)
- D-26 (movement no GML ref) → PARTIALLY closed by 06-13 (constants derived) + 06-16 (sprite state); reopened as D-41/D-42/D-43/D-44
- D-27 (no real assets) → PARTIALLY closed by 06-14 (asset-pipeline bootstrap + mvp-room); reopened as D-39/D-40

**Operator approval:** decidel, 2026-05-11 — **NOT APPROVED.** Eight new findings (D-39..D-46) block CLI-08 closure. Phase 6 status remains IN_PROGRESS; spawn **Phase 6.1** (or `/gsd-plan-phase 6 --gaps` second cycle) to address D-39..D-46.

**Phase 6 status (2026-05-11):** IN_PROGRESS — second gap-closure cycle required.
