---
status: diagnosed
phase: 06.7-network-model-client-trust-fall-trigger
source: [06.7-01-SUMMARY.md, 06.7-02-SUMMARY.md, 06.7-03-SUMMARY.md, 06.7-04-SUMMARY.md, 06.7-HUMAN-UAT.md]
started: 2026-05-17T10:18:59Z
updated: 2026-05-17T10:34:30Z
---

## Current Test

[testing complete]

## Tests

### 1. Cold Start Smoke Test
expected: Kill running server/client. Start fresh. Server boots clean, client handshake succeeds at PROTOCOL_VERSION=4, join BNCentral returns live state.
result: pass
note: Operator connected two clients to staging successfully; cold-start path implicitly validated by subsequent multiplayer test.

### 2. Diagonal-stop drift bug closed (D-02a)
expected: Log in to BNCentral spawn. Hold W+D for 2 s for up-right diagonal. Release both keys simultaneously. Sprite stops at its current pixel position with no horizontal shift and no ~10 px snap right or left.
result: pass
reported: "diagonal stop drift fixed"

### 3. Dropped-packet hitching closed (D-02b)
expected: DevTools → Network → Throttling = Slow 3G (or custom ~5% loss / 150 ms RTT). Hold W to run north 5 s. Continuous smooth movement.
result: pass
reported: "no more dropped-packet hitching, silky smooth for local movement"

### 4. Two-player smoke (REQ-CLI-08 hard milestone regression)
expected: Operator A + B in two browsers join BNCentral. Each sees the other's sprite and smooth motion (no rubber-band, no drift), running animation plays, velocity is applied. Chat appears with correct origin.
result: issue
reported: "client of 2nd joiner doesn't see any remote player position or sprite updates; client of 1st joiner does see player position updates, but they're super slow, inconsistent timing, facing direction comes through but never sprite change (running anim never plays), velocity is never applied; chat works"
severity: blocker

### 5. PROTOCOL_VERSION 3 client rejection — AUTOMATED COVERAGE (D-12)
expected: `pnpm -C apps/server exec vitest run test/protocol-v4-handshake.integ.test.ts` exits 0. v3 rejected with close-code 4400 / PROTOCOL_VERSION_MISMATCH; v4 accepted.
result: pass
note: First run produced 524≠4400 + 'seat reservation expired' failures — diagnosed as stale packages/protocol build artifact. After `pnpm -C packages/protocol build`, all 3 tests pass at HEAD 9816275 (3.34s, 0 failures). Server correctly throws ServerError(4400, "PROTOCOL_VERSION_MISMATCH ...") at apps/server/src/RebnoRoom.ts:394-398.

### 6. Rollback drill (D-14)
expected: Confirm documented rollback target references the four Phase 06.7 plan commits.
result: pass
note: Verified in 06.7-04-SUMMARY.md "Rollback Target" section — lists Plan 01 (1ba1fe6), Plan 01 summary (086bf71), Plan 02 (f9b02e3), Plan 03 (8ba0f9b), Plan 04 docs commit. Four production commits accounted for.

### 7. Idle-cadence steady 30 Hz network confirm (D-06)
expected: Idle BNCentral 5 s with WS frame panel open. ~150 position_update frames (120–180 range), each with vx=0/vy=0, no gaps >100 ms.
result: skipped
reason: Operator did not run DevTools WS frame measurement during this UAT pass. Automated coverage in apps/client/src/__test__/position-dispatcher.test.ts is the standing guard; manual measurement deferred until Test 4 blocker resolved (remote-sync issues may also indicate dispatcher cadence problems).

## Summary

total: 7
passed: 4
issues: 1
pending: 0
skipped: 1
blocked: 0
new_regressions: 1
gaps_diagnosed: 5

## Gaps

- truth: "Each peer sees the other's player position, velocity, facing, and animation state in near-real-time"
  status: failed
  reason: "User reported: client of 2nd joiner doesn't see ANY remote player position or sprite updates from 1st; 1st sees super slow inconsistent updates from 2nd"
  severity: blocker
  test: 4
  root_cause: "RC #1 — Wire-schema vs runtime-timestamp mismatch. cPositionUpdateSchema (packages/protocol/src/intents.ts:125-137) declares monotonic_at_ms as .int(); PositionDispatcher passes Phaser fractional rAF _time arg unmodified (position-dispatcher.ts:30-46, GameScene.ts:1193 sendTick(_time)). Every frame fails zod parse → server logs invalid_intent and silently drops (onMessageHandlers.ts:315-324). Only frames landing on integer-ms boundaries pass — hence super-slow inconsistent timing. For 2nd joiner viewing 1st (mostly idle pre-join), nearly all updates dropped → sprite frozen at spawn → 'sees nothing'."
  artifacts:
    - path: "apps/client/src/prediction/position-dispatcher.ts"
      issue: "Lines 30-46 pass monotonic_at_ms through unmodified, unlike x/y/vx/vy which ARE rounded"
    - path: "apps/client/src/scenes/GameScene.ts"
      issue: "Line 1193 passes Phaser fractional _time to sendTick"
    - path: "packages/protocol/src/intents.ts"
      issue: "Lines 125-137 .int() constraint on monotonic_at_ms (correct — wire contract)"
    - path: "apps/client/src/__test__/position-dispatcher.test.ts"
      issue: "Unit tests use integer time args (sendTick(1), sendTick(1234)) — masked runtime drift from CI"
  missing:
    - "Apply Math.floor(monotonic_at_ms) at position-dispatcher.ts:42 (preserve u32-int wire contract)"
    - "Add regression test: dispatcher.sendTick(1234.567) → payload monotonic_at_ms === 1234"
  debug_session: ".planning/debug/two-player-remote-sync-broken.md"

- truth: "Remote-player velocity (vx, vy) is applied to position interpolation/extrapolation"
  status: failed
  reason: "User reported: velocity is never applied"
  severity: blocker
  test: 4
  root_cause: "RC #2 — PredictionEngine.getLocalState() always returns vx=0, vy=0. step.ts:243-251 (with-input branch) hardcodes vx:0, vy:0 per BNO instant-set model (ACCEL=0, FRICTION=0). predictor.ts:113-114 propagates to localState. position-dispatcher.ts:37-38 Math.round(local.vx/vy) → always 0 on wire. Server echoes 0; remote renderer never animates."
  artifacts:
    - path: "packages/game-logic/src/step.ts"
      issue: "Lines 243-251 with-input branch returns vx:0, vy:0 unconditionally"
    - path: "apps/client/src/prediction/predictor.ts"
      issue: "Lines 113-114 propagate step's vx=0/vy=0 to localState"
    - path: "apps/client/src/prediction/position-dispatcher.ts"
      issue: "Lines 37-38 read local.vx/vy which are always 0"
  missing:
    - "Inject axis-vector callback into PositionDispatcher (parallel to getFacing / getIsRunning)"
    - "Compute vx = round(axisX * RUN_SPEED_PX_PER_TICK), vy = round(axisY * RUN_SPEED_PX_PER_TICK) — mirror GameScene.ts:1199-1200 pattern used for local renderer's onSimulationTickLocal"
  debug_session: ".planning/debug/two-player-remote-sync-broken.md"

- truth: "Remote-player sprite plays running animation when vx/vy != 0"
  status: failed
  reason: "User reported: facing comes through but sprite change never happens, running anim never plays"
  severity: blocker
  test: 4
  root_cause: "RC #3 — Remote-render reads vx/vy only, ignores broadcast anim_state. GameScene.ts:1213-1226 remote-loop calls playerRenderer.onSimulationTickRemote(sessionId, p.vx, p.vy, p.x, p.y); never consumes p.anim_state. PlayerRenderer.ts:463-470 derives isRunning = hypot(vx,vy) > 0 — with vx=0/vy=0 from RC #2, always Stand frame. Dispatcher DOES author anim_state correctly via packAnimState(facing, getIsRunning()) and PlayerState.anim_state IS @type-decorated; consumer just ignores it."
  artifacts:
    - path: "apps/client/src/scenes/GameScene.ts"
      issue: "Lines 1213-1226 remote-sim-tick loop ignores p.anim_state"
    - path: "apps/client/src/render/PlayerRenderer.ts"
      issue: "Lines 463-470 derive isRunning from vx/vy instead of consuming explicit anim_state"
  missing:
    - "Pass unpackAnimState(p.anim_state).running as new arg to onSimulationTickRemote (principled — server anim_state is single source of truth)"
    - "OR rely on RC #2 fix making vx/vy non-zero during movement (cheaper consequence of RC #2)"
  debug_session: ".planning/debug/two-player-remote-sync-broken.md"

- truth: "Right-edge and bottom-edge floor collision stop position matches pre-06.7 baseline"
  status: failed
  reason: "User reported: right-edge and bottom-edge floor collision both stop one pixel farther away (on the respective axis) than before"
  severity: minor
  test: 4
  root_cause: "Boundary-convention mismatch between two collision paths in packages/game-logic/src/collision.ts. Client-side walkable-grid path (isFeetBboxWalkable line 172) treats feet-bbox right/bottom corner pixel as INCLUSIVE via Math.floor(px/tile_w); legacy polys path (feetBboxIntersectsAABB lines 69-72) treats it as EXCLUSIVE via strict feetMaxX > minX. The two paths produce stops differing by exactly 1 px on MAX axes (right, bottom) and agree on MIN axes (left, top) — matches operator report exactly. Pre-existed since Plan 06.1-02 (0c40432) but was masked: pre-06.7 ReconcileEngine.onServerSnapshot called predictor.applyServerSnapshot for self-player, replacing predicted position with server polys-resolved position. Phase 06.7 Plan 03 (8ba0f9b) added early-return for self-player in reconciler.ts:53-54 — removed corrective sink, exposed grid-path 1-px-short stop."
  artifacts:
    - path: "packages/game-logic/src/collision.ts"
      issue: "Line 172 — Math.floor in isFeetBboxWalkable causes inclusive-corner asymmetry (load-bearing line)"
    - path: "packages/game-logic/src/collision.ts"
      issue: "Lines 63-74 — feetBboxIntersectsAABB uses strict inequality (other half of mismatch)"
    - path: "packages/game-logic/src/step.ts"
      issue: "Lines 179-225 — branch selector routes client to grid path"
    - path: "apps/client/src/prediction/reconciler.ts"
      issue: "Lines 53-54 — isSelfPlayer early-return that exposed pre-existing asymmetry"
  missing:
    - "Normalize boundary conventions: make isFeetBboxWalkable's right/bottom corner sample exclusive of bbox boundary"
    - "Subtract 1 from right/bottom probe pixels at call sites step.ts:194-195 and step.ts:214-215 (intent explicit there); OR adjust inside isFeetBboxWalkable for right/bottom only"
    - "Add regression test pinning right-edge AND bottom-edge stop pixels against tile-boundary wall for known walkable grid"
  debug_session: ".planning/debug/edge-collision-off-by-one.md"

## Resolved (false positive)

- truth: "PROTOCOL_VERSION 3 client rejection emits close-code 4400 with PROTOCOL_VERSION_MISMATCH message"
  status: resolved_no_fix
  reason: "Initial run reported AssertionError: expected 524 to be 4400. Diagnosed as stale packages/protocol build. After `pnpm -C packages/protocol build`, all 3 tests pass at HEAD 9816275. Server correctly throws ServerError(4400, 'PROTOCOL_VERSION_MISMATCH ...') at apps/server/src/RebnoRoom.ts:394-398; @colyseus/ws-transport preserves 4400 through to SDK MatchMakeError.code."
  severity: n/a
  test: 5
  debug_session: ".planning/debug/v3-rejection-wrong-close-code.md"
  note: "Operator process improvement: ensure pnpm -C packages/protocol build runs before integ tests after any protocol schema/version change."
