# Phase 06.3 — Discussion Log

**Session:** 2026-05-13 (cycle-4 closeout → cycle-5 scoping)
**Mode:** /gsd-discuss-phase default
**Phase status entry:** 06.2 CLOSED NOT APPROVED @ e4dcb3a, 06.3 inserted via /gsd-phase --insert (commit 3afeed9), then discuss.

---

## Gray Areas Presented (multiSelect)

> Phase 06.3: cycle-4 gap-closure
> Domain: Close cycle-4 UAT regressions on staging build 688d334. Re-run human UAT end-to-end. Capture CLI-08 milestone mp4 on PASS.

**Options presented (6 candidates, trimmed to 4 by AskUserQuestion max):**

- ☐ D-40/D-54 fix scope
- ☐ D-45 attack pattern
- ☐ D-51 evict crash diagnosis
- ☐ D-55/D-56 scope decision (dropped to fit 4-max)
- ☐ D-52/D-57 fold-or-split (dropped to fit 4-max — Claude defaulted)
- ☐ GREEN gate uplift

**Final presented (4):** D-40/D-54 fix scope, D-45 attack pattern, D-51 evict crash diagnosis, GREEN gate uplift.

**Operator selected:** all 4.

---

## Area 1 — D-40/D-54 fix scope

**Q1: How scope fix?**
- Spike first — trace payload end-to-end ✓ (operator pick)
- Direct fix — assume collision_polys missing
- Split: collision-extent + outer-gray separately

**Q2: Which rooms instrument?**
- MVP-lobby only
- All rooms in RoomRegistry
- MVP-lobby + 1 sanity-check room ✓ (operator pick)

**Q3: Spike telemetry surface?**
- window.__rebno (same as 06.2-03)
- Server log + client console
- Both — window.__rebno + server log ✓ (operator pick)

**Notes / refs surfaced:**

- `packages/protocol/src/intents.ts:155-200` — newLayoutSchema does NOT define `collision_polys`; legacy shape (115-153) does. Schema gap is a real hypothesis.
- `apps/client/src/render/RoomCollision.ts:23-60` — client derives walkable grid from `tiles[]`; sparse tiles[] coverage propagates to sparse collision.
- 06.2-05 commit shipped tiles[] only (H1 fix path); broadcast did not extend to collision payload.

---

## Area 2 — D-45 attack pattern

**Q1: Plan structure?**
- Single combined investigation + fix plan
- Spike + fix (flicker first, offset/depth second)
- Three separate plans (offset / flicker / depth) ✓ (operator pick)

**Q2: D-45 offset — derive correct formula from?**
- Re-read legacy BNO Draw.dnd.json
- Operator visual iteration
- Both — legacy spec primary, visual confirm secondary
- **Operator answer (freeform):** "we have historically referenced legacy. there must be a key difference between the font rendering tech here. here's what i can tell you for sure: the Y dist between the bottom of an 'a' and the top of the player's head is 10px in legacy, and 25px in the new client" (image attached confirming overshoot).

**Q3: D-45 offset — which fix path? (added after operator measurement)**
- Retune NAMETAG_OFFSET_Y constant (16 → 1) ✓ (operator pick)
- Glyph-metric anchoring (canvas measureText) — deferred to Phase 7
- Switch to BitmapText (no padding) — deferred to Phase 7

**Q4: D-45 flicker root cause — likely path?**
- Spike first — instrument flicker source ✓ (operator pick, with refinement)
- Direct fix — floor to integer Y
- Direct fix — pin to integer-floored sprite.y
- **Operator refinement:** "it only happens on remote players, so the spike log would need to capture i.e. the first connected remote player, rather than the local player."

**Q5: D-45 depth z-order — fix approach?**
- setDepth(NAMETAG_DEPTH=10000) constant
- Layer-based depth (Phaser Layer / Container)
- Per-frame depth = sprite.y + 10000
- **Operator answer (freeform):** "there may be some things in game which overlap nameplates, but i'm not sure what they might be. please reference legacy and replicate how it's done there."

**Notes / refs surfaced:**

- `apps/client/src/render/Nameplate.ts:42` — NAMETAG_OFFSET_Y constant.
- `apps/client/src/render/Nameplate.ts:98-100` — follow() formula.
- `apps/client/public/atlas-mvp.json` — NaviStandD + NaviRunD both 36×48 (frame-bbox flicker hypothesis weak).
- Phaser Text bbox includes line-height descender padding below visible glyph; legacy GM draw_text doesn't. This explains the 15-px overshoot operator measured.
- Legacy depth_set refs: `extracted/client-5-8/objects/0042-player/events/Draw.dnd.json` + `extracted/client-5-8/objects/0000-server/events/Draw.dnd.json`.

---

## Area 3 — D-51 evict crash diagnosis

**Q1: Which path?**
- Spike-first — reproduce locally + trace broadcast ✓ (operator pick, with H1 disproof)
- Direct fix on most-likely hypothesis (cookie/localStorage)
- Stage spike+fix per hypothesis
- **Operator refinement:** "hypothesis 1 disproven. they were different chrome profiles. -spike first"

**Q2: Spike scope — what instrument?**
- Full envelope — server outbound + client inbound + onJoin timing
- Client-side only — onerror + handler trace
- Playwright reproduction first; instrumentation if can't repro
- **Operator delegated:** "agent decides using best judgment" → Agent picked **hybrid** (Playwright multi-tab fixture as primary + full envelope instrumentation in parallel). Fixture forces deterministic repro; envelope ensures root cause readable.

**Q3: Fix contingency — if spike pinpoints H2 (seat race), is single-cycle fix OK?**
- Single-cycle: spike + fix in 06.3 ✓ (operator pick)
- Spike-and-defer — fix to 06.4 if non-trivial
- Hard-close in 06.3 — disable dup-login on client too

**Notes / refs surfaced:**

- `apps/server/src/RebnoRoom.ts:409-495` — current eviction code looks correct; spike must reveal why BOTH tabs crash despite scoping.
- H1 (shared cookie/localStorage) disproved by operator: different Chrome profiles.
- Remaining hypotheses: H2 (seat-reservation race in Colyseus 0.17), H3 (Schema delete auto-broadcast side-effect on new-tab during onJoin), H4 (client-side force_reset handler error).
- Colyseus 0.17 → 0.18 bump explicitly out-of-scope this cycle even if spike points there; would escalate to own phase.

---

## Area 4 — GREEN gate uplift

**Q1: What MUST 06.3 add beyond unit tests? (multiSelect)**
- Playwright e2e all GREEN on staging pre-merge
- Operator visual confirmation pre-merge
- E2e test reviews assertion-not-just-pass
- Staging redeploy + smoke gate per fix
- **Operator delegated:** "agent decides using best judgment" → Agent picked **all four**, weighted: (1)+(2)+(3) HARD gates, (4) SOFT (per-Wave-2-boundary, not per-fix).

**Q2: Enforcement — how structurally bake in?**
- PLAN.md per-plan gate checklist
- Wave-3 hard verification with explicit checkboxes
- Both — per-plan checklist + Wave-3 final gate
- **Operator delegated:** "agent decides using best judgment" → Agent picked **option 3 (both)**. Defense-in-depth: per-plan catches mid-cycle drift; Wave-3 catches phase-level closure drift.

**Notes / refs surfaced:**

- Anti-recurrence pattern: cycle-3 `cli-08-nameplate-offset.e2e` PASS but caught nothing because expectation matched the (overshot) code. E2e assertion canonical-ref cite is the structural fix.
- 06.1 ✗ → 06.2 ✗ — third NOT APPROVED would be a serious workflow signal. Phase 06.3 verification is treated as load-bearing.

---

## Discretion items (Claude defaults + operator overrides)

**Defaults presented:**

- D-55 (teleport in/out anim) — DEFER Phase 7
- D-56 (chat box readability) — INCLUDE 06.3
- D-52/D-57 — FOLD shared diagnostic spike
- D-53 (spawn delay invisible) — small plan: publish + audit ensureLocal
- CLI-08 mp4 capture — on PASS verdict

**Operator overrides (freeform):** "Override D-55 — include in 06.3, roll in a plan to fix newly-identified bug: player sprite sometimes desyncs after player stops moving. so remote will look different from local --- and another one: in the screenshot, the only nameplate with the right font is 'uat_a' on uat_b's client. all of the 3 other nameplates are rendering with the old font (not fixedsys)"

→ Two NEW findings surfaced mid-discuss:

- **D-58** — player sprite position desync after movement stops (remote view ≠ local view in steady state). Captured as reconciler-family.
- **D-59** — nameplate font inconsistency. Only 1 of 4 nameplates renders Fixedsys. Hypothesis: WOFF2 document.fonts.ready race; Phaser Text snapshots font-availability at construction.

**Q (follow-up): D-58 spike or fix?**
- Fold into D-52/D-57 reconciler diagnostic spike ✓ (operator pick)
- Separate spike — idle-state convergence pin-pointed
- Direct fix — force re-sync on Stand state

**Q (follow-up): D-59 spike or fix?**
- Direct fix — gate Phaser boot on document.fonts.ready ✓ (operator pick)
- Direct fix — re-render nameplates on font-load event
- Spike first — confirm WOFF2 race

---

## Deferred Ideas

- Colyseus 0.17 → 0.18 upgrade (only if D-51 spike requires it; escalates to own phase).
- Glyph-metric-aware nameplate anchoring (alternative to D-45 Plan A constant retune; durable but heavier — Phase 7).
- BitmapText / sprite-font glyphs (removes both font-metric padding + WOFF2 race — Phase 7).
- Multi-room collision/payload generalization beyond MVP-lobby + sanity-check (Phase 7).
- All Phase 06.1/06.2 cumulative defers (falls/abyss, ice tiles, Hexport, JokerShell, multi-floor, persistCharacter FK, prod provisioning).

---

## End-state

CONTEXT.md committed; checkpoint cleaned. Next: `/gsd-plan-phase 06.3`.
