---
phase: 06.4
plan: 02
subsystem: client-render + server-rooms-data
tags: [D-61, gap-closure, wall-border-drop, room-data]
requires:
  - 06.1-05  # walkable-grid derivation supersedes wall_border
provides:
  - "mvp-room layout no longer emits or renders the legacy gray-border block"
  - "Phase-7 / mvp-lobby compat preserved via schema-optional wall_border"
affects:
  - apps/server/rooms/mvp-room/000.json (data drop + manifest re-sign)
  - apps/client/src/render/RoomRenderer.ts (render block + dead state removal)
  - tools/room-converter/src/mvp-room.ts (builder no longer emits the field)
  - tools/room-converter/test/mvp-room.test.ts (inverted assertion)
  - apps/client/src/__test__/room-renderer.test.ts (NEW)
tech-stack:
  added: []
  patterns:
    - "Canonical JSON stringifier (sorted keys + LF + trailing-newline) for Ed25519 manifest signature stability"
    - "vitest mock-Phaser pattern for render-time assertion (PATTERNS.md §lines 966-991)"
key-files:
  created:
    - apps/client/src/__test__/room-renderer.test.ts
  modified:
    - apps/server/rooms/mvp-room/000.json
    - apps/server/rooms/mvp-room/000.sig
    - apps/client/src/render/RoomRenderer.ts
    - tools/room-converter/src/mvp-room.ts
    - tools/room-converter/test/mvp-room.test.ts
decisions:
  - "wall_border kept .optional() in newLayoutSchema (intents.ts) — Phase 7 / mvp-lobby compat untouched"
  - "Dropped client diagnostic payloadWallBorder from window.__rebno (legacy reference)"
  - "Stripped all wall_border textual references from RoomRenderer.ts to satisfy plan grep-0 verification (kept on protocol-schema side only)"
  - "Server pino diagnostic field wallBorder in broadcastRoomLayout retained for one cycle (out of scope; harmless)"
metrics:
  duration_minutes: 35
  completed: 2026-05-15T11:47:36Z
  tasks: 2
  commits: 3
requirements:
  - REQ-CLI-06
  - REQ-CLI-08
---

# Phase 06.4 Plan 02: D-61 wall_border drop Summary

Removed the legacy `wall_border` data block from `apps/server/rooms/mvp-room/000.json` and the corresponding `if (layout.wall_border) { ... }` render branch in `apps/client/src/render/RoomRenderer.ts`; walkable-grid (06.1-05) + the upcoming server-runtime collision derivation (06.4-08 D-62) supersede the legacy gray-border fallback. Protocol schema retains `wall_border` as `.optional()` for Phase 7 / mvp-lobby compatibility.

## Tasks

| Task | Name                                                                    | Commit  | Files                                                                                                        |
| ---- | ----------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| 1    | Drop wall_border from mvp-room/000.json + re-sign manifest              | c46f075 | apps/server/rooms/mvp-room/000.json, apps/server/rooms/mvp-room/000.sig, tools/room-converter/src/mvp-room.ts, tools/room-converter/test/mvp-room.test.ts |
| 2 RED | Add failing room-renderer wall_border drop assertions                   | a92fd37 | apps/client/src/__test__/room-renderer.test.ts                                                               |
| 2 GREEN | Drop wall_border render block + dead state                            | 97abc16 | apps/client/src/render/RoomRenderer.ts, apps/client/src/__test__/room-renderer.test.ts                       |

## What Was Built

**1. Data drop + manifest re-sign (Task 1)**

- Removed the `"wall_border": { ... }` block (10 lines) at the end of `apps/server/rooms/mvp-room/000.json`.
- Re-canonicalized the JSON (sorted-keys + 2-space indent + LF + trailing newline) using the same `canonicalize()` shape used by `tools/room-converter/src/mvp-room.ts:buildMvpRoom`.
- Re-signed against the project Ed25519 key (`./keys/rebno-room-signing.ed25519`) using the existing `manifestPayload(room_id, rev, sha256(json))` envelope and wrote `apps/server/rooms/mvp-room/000.sig` (64 raw bytes).
- Verified post-write: `crypto.verify(null, manifestPayload('mvp-room', '000', json), pub, sig) === true`.
- Updated the source-of-truth builder `tools/room-converter/src/mvp-room.ts` to no longer emit `wall_border` (`wallColor` option removed) — future `pnpm room-converter:mvp-room` runs will not reintroduce the field.
- Inverted the existing `room-converter` test assertion: `'wall_border' in layout === false`.

**2. Render block + dead state removal (Task 2)**

- Deleted the entire `if (layout.wall_border) { ... }` branch (formerly RoomRenderer.ts:273-311) that emitted four Phaser.Rectangle strips per edge.
- Removed the now-dead `private wallRects: Phaser.GameObjects.Rectangle[]` field declaration + its dispose-time destroy loop (no producer pushes to it after the block removal). `this.group.clear(true, true)` handles all floor / TSide1 sprite teardown.
- Dropped the `wall_border?` field from the renderer's local `NewRoomLayout` interface and the `payloadWallBorder` diagnostic on `window.__rebno`.
- Stripped all remaining `wall_border` textual references from comments in RoomRenderer.ts to satisfy the plan's strict `grep -n wall_border RoomRenderer.ts → 0 matches` verification.
- Protocol-side schema (`newLayoutSchema` in `packages/protocol/src/intents.ts`) untouched — `wall_border` remains `.optional()` for Phase 7 / mvp-lobby compat. Any payload still carrying the field is silently dropped on the client by structural typing.

**3. Drop-assertion test (Task 2, NEW file)**

- `apps/client/src/__test__/room-renderer.test.ts` — built per PATTERNS.md §lines 966-991 (vitest mock-Phaser) + §lines 815-822 (room-renderer.test.ts analog). Mocks `phaser` + provides a stub Scene whose `add.rectangle` / `add.sprite` / `add.image` are `vi.fn()` spies. Two test cases:
  1. Layout WITHOUT `wall_border` (post-D-61 mvp-room shape) → `scene.add.rectangle` not called.
  2. Layout WITH `wall_border` (legacy / Phase-7 mvp-lobby compat shape) → `scene.add.rectangle` still not called (render block deleted).
- Both cases pass; RED-gate proven before GREEN (case 2 failed pre-implementation with 4 calls; passed post-implementation with 0 calls).

## Verification Outcomes

- `node -e "const j=require('./apps/server/rooms/mvp-room/000.json'); if('wall_border' in j) process.exit(1);"` → exit 0 ("OK: no wall_border").
- `grep -n "wall_border" apps/server/rooms/mvp-room/000.json` → 0 matches.
- `grep -n "wall_border" apps/server/rooms/mvp-lobby/000.json` → 0 matches (lobby is legacy shape — the plan's reading of mvp-lobby was inaccurate; lobby never emitted the new-shape wall_border block. See Deviations below.).
- `grep -n "wall_border" apps/client/src/render/RoomRenderer.ts` → 0 matches.
- `grep -n "wall_border" packages/protocol/src/intents.ts` → still present at line 173 as `.optional()` (schema unchanged).
- `cd apps/client && pnpm test --run room-renderer` → 2 pass.
- `cd apps/client && pnpm typecheck` → exit 0 (clean).
- `cd apps/client && pnpm test` (full suite) → 27 files, 195 tests, 4 todo, 0 failures.
- `pnpm room-converter:test` → 11 tests pass (includes the inverted `wall_border absent` assertion).
- `node -e "crypto.verify(null, manifestPayload('mvp-room', '000', json), pub, sig)"` → `true` (signature regenerated cleanly).

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing critical functionality] Updated tools/room-converter/src/mvp-room.ts source-of-truth**

- **Found during:** Task 1.
- **Issue:** Plan only specified editing `apps/server/rooms/mvp-room/000.json` directly. But `mvp-room/000.json` is *generated* by `tools/room-converter/src/mvp-room.ts` via `pnpm room-converter:mvp-room`. Editing the generated artifact without updating the generator means the next regeneration would silently re-emit `wall_border` — a correctness regression.
- **Fix:** Removed the `wall_border` block + `wallColor` option from `buildMvpRoom`; inverted the corresponding test in `tools/room-converter/test/mvp-room.test.ts` to assert the key is absent.
- **Files modified:** `tools/room-converter/src/mvp-room.ts`, `tools/room-converter/test/mvp-room.test.ts`.
- **Commit:** c46f075.

**2. [Rule 2 - Missing critical functionality] Dropped client diagnostic `payloadWallBorder` from `window.__rebno`**

- **Found during:** Task 2 (during the strict grep-0 verification cleanup).
- **Issue:** Plan focused on removing the render block, but a diagnostic line `const payloadWallBorder = layout.wall_border ?? null;` + a `payloadWallBorder` key on the `window.__rebno` telemetry object remained. Plan's `<verification>` grep requires `wall_border → 0 matches` in `RoomRenderer.ts`.
- **Fix:** Removed the diagnostic line + telemetry field; added a comment noting the removal and pointing operators to the server-side pino log (`broadcastRoomLayout`) for one transitional cycle.
- **Commit:** 97abc16.

**3. [Rule 1 - Bug] Test-fixture type cast TS2352 error**

- **Found during:** Task 2 GREEN typecheck after the test file was added.
- **Issue:** `as Record<string, unknown>` cast from `RoomLayout` violated TS structural-overlap rule.
- **Fix:** Routed through `as unknown` first (`as unknown as Record<string, unknown>`) to mute the (correct) compiler warning about non-overlap.
- **Commit:** 97abc16.

### Plan-Reality Deltas (informational, no source change)

- **mvp-lobby shape:** The plan's `<verification>` step expected `grep -n "wall_border" apps/server/rooms/mvp-lobby/000.json → still present`. In this repo at the merge base `3fd10744`, `mvp-lobby/000.json` is the *legacy* shape (`tile_grid`, `collision_polys`, `room_size`, ...) and never emitted a new-shape `wall_border` block. No change made to mvp-lobby; the schema-optional retention on the protocol side preserves the documented compat intent.
- **Server-side pino diagnostic:** `apps/server/src/RebnoRoom.ts:270` still logs `wallBorder: layoutRecord.wall_border ?? null` in the `broadcastRoomLayout` pino event. This is harmless (always logs `null` post-D-61) and outside the plan's `<files_modified>` scope. Kept for one cycle for operator audit traceability; can be retired in 06.4-08 D-62 follow-up.

## Authentication Gates

None — fully autonomous execution.

## Threat Flags

None — no new surface added.

## Self-Check: PASSED

- [x] apps/server/rooms/mvp-room/000.json → FOUND (wall_border absent; LF endings; canonical JSON)
- [x] apps/server/rooms/mvp-room/000.sig → FOUND (64 bytes; Ed25519 verifies cleanly against committed pub.pem)
- [x] apps/client/src/render/RoomRenderer.ts → FOUND (0 wall_border matches; wallRects field removed)
- [x] apps/client/src/__test__/room-renderer.test.ts → FOUND (NEW; 2 tests pass)
- [x] tools/room-converter/src/mvp-room.ts → FOUND (wall_border emission removed)
- [x] tools/room-converter/test/mvp-room.test.ts → FOUND (inverted assertion passes)
- [x] Commit c46f075 → FOUND in git log (Task 1 — data drop + manifest re-sign)
- [x] Commit a92fd37 → FOUND in git log (Task 2 RED — failing test)
- [x] Commit 97abc16 → FOUND in git log (Task 2 GREEN — render block removed)
