# Phase 06.1: gap-closure D-39..D-46 (UAT 2026-05-11) - Discussion Log

> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.

**Date:** 2026-05-11
**Phase:** 06.1-gap-closure-d-39-d-46-uat-2026-05-11
**Areas discussed:** D-39 transparency strategy, D-44 run-speed + animation + input, D-45 nametag font, D-46 camera, MVP background, depth_set port, TSide1 tile sides, NaviMask collision + walkable-region

---

## D-39 transparency strategy

| Option | Description | Selected |
|--------|-------------|----------|
| Flag + top-left sentinel | Canonical GameMaker rule: per-sprite `transparent` flag drives keying; sample top-left pixel of frame 0 as the sentinel | |
| Flag + fixed #FFFFFF | Simpler keying with fixed white | |
| Detect sentinel + override file | Top-left sampling default, override JSON for edge cases | |
| (free-text) | Operator: "find a solution that can leverage the canonical gamemaker route, while also supporting whatever transparency method is most common for asesprite assets" | ✓ |

**User's choice:** Two-path pipeline. Bootstrap (legacy BMP) = canonical GameMaker flag-driven top-left sentinel. Build (Aseprite re-export PNG) = trust embedded alpha as-is.
**Notes:** Captures both legacy faithfulness and Aseprite-native dev ergonomics in two distinct codepaths.

### D-39 strict match

| Option | Description | Selected |
|--------|-------------|----------|
| Exact RGB match | BMPs are 8-bit paletted, no anti-alias fringe | ✓ |
| Tolerance ±2 | Safety net | |
| Per-sprite tolerance config | Override JSON | |

### D-39 guard

| Option | Description | Selected |
|--------|-------------|----------|
| Unit test on output PNG | Assert alpha present + zero sentinel-pixel matches | ✓ |
| Unit + golden hash | Heavier reproducibility-grade | |
| Unit + visual diff | pixelmatch vs reference PNGs | |

### D-39 rebuild trigger

| Option | Description | Selected |
|--------|-------------|----------|
| Plan triggers rebuild + commit | Plan re-runs bootstrap + build, commits regenerated artifacts | ✓ |
| Pipeline test-only, manual rebuild | Operator runs manually | |
| CI auto-rebuild on bootstrap diff | CI job regenerates | |

---

## D-44 run-speed source

| Option | Description | Selected |
|--------|-------------|----------|
| RUN=5, drop WALK | Citation: KeyPress-82.gml — canonical Ctrl+R toggle target | ✓ |
| RUN=5, keep WALK deprecated | Migration scaffolding | |
| Pick different value | Operator-specified | |

**Notes:** Citation `extracted/client-5-8/objects/0000-server/events/KeyPress-82.gml:5-12` settles this — Ctrl+R toggles curspeed 3↔5; 5 is the canonical run value.

### D-44 animation rate

| Option | Description | Selected |
|--------|-------------|----------|
| frames-per-tick = 0.5 | Match GML image_speed = 0.5 | |
| Derive per-sprite from frame_count | Per-sprite cycle rate | |
| Fixed N FPS | Compute frames-per-tick from target FPS | |
| (free-text) | Operator: "match GML. note that player curspeed could be affected by environmental assets like conveyor platforms. best to keep the math in, rather than falling back to a constant." | ✓ |

**Notes:** Lock the canonical formula `image_speed = curspeed / 10`. Per-entity `curspeed` is forward-compat with SRV-14 moving platforms / conveyors.

### D-41/D-42 callsite contract

| Option | Description | Selected |
|--------|-------------|----------|
| Both = sim-tick callback | Single 30 Hz contract for local + remote | ✓ |
| Local = sim-tick, Remote = interp-tick | Two contracts | |
| Both = render frame + delta scaling | Phaser render-frame driven | |

### D-44 input model

| Option | Description | Selected |
|--------|-------------|----------|
| Arrows only, drop alt-walk | Pure WASD/arrow = run | |
| Keep alt-stand | Hold Alt to freeze | |
| WASD + Arrows both | Both control schemes | |
| (free-text) | Operator: "keep alt-stand functionality. change the trigger to Shift instead" | ✓ |

**Notes:** Modern Shift-held = stand-in-place; replaces legacy Alt.

---

## D-45 nametag

| Option | Description | Selected |
|--------|-------------|----------|
| Web font @font-face (Fixedsys Excelsior) | WOFF2 recreation | ✓ |
| BNO sprite-strip bitmap font | Generate atlas first | |
| Phaser BitmapText with another pixel font | Press Start 2P / m6x11 | |

### D-45 color rule

| Option | Description | Selected |
|--------|-------------|----------|
| Local cyan, remote white | Faithful (citation: Draw.dnd.json local + remote) | ✓ |
| All cyan | UAT screenshot uniform | |
| All white with shadow | Modern uniform | |

### D-45 size + shadow

| Option | Description | Selected |
|--------|-------------|----------|
| 9-10px + 1px black shadow | Operator-requested shadow for legibility | ✓ |
| 9-10px no shadow | Period-accurate | |
| 12px + shadow | Larger | |

### D-45 position

| Option | Description | Selected |
|--------|-------------|----------|
| Exact BNO math y-16 | x+(sprite_width/2)-(string_width(name)/2), y-16 | ✓ |
| y - sprite_height/4 - line_height | Dynamic | |
| Configurable per-sprite | meta.json offset | |

---

## D-46 camera

| Option | Description | Selected |
|--------|-------------|----------|
| BNO 32x32 deadzone | Per BNCentral view (hBorder=304, vBorder=224 → 32x32) | ✓ |
| Hard-center (0, 0) | Modern feel | |
| Per-room from meta | Future-flex | |

### D-46 root-cause fix

| Option | Description | Selected |
|--------|-------------|----------|
| Defer startFollow until local sprite exists | Race fix + self-heal re-bind | ✓ |
| Bind on first sim-tick | Implicit | |
| Investigate first (read-only spike) | Spike before fix | |

### D-46 bounds

| Option | Description | Selected |
|--------|-------------|----------|
| setBounds(0, 0, roomW, roomH) | Lock to room edges | |
| Unbounded | No bounds | |
| Bounds + roundPixels=true | Bounded with HiDPI | |
| (free-text) | Operator: "unbounded + roundPixels. the game world should never feel like it has 'edges,' so the camera should be able to scroll past the room edges. the background should still render outside the room edges." | ✓ |

**Notes:** Camera unbounded; roundPixels=true for HiDPI; background must render past room edges (motivated discovery of bkdraw object).

---

## MVP background

| Option | Description | Selected |
|--------|-------------|----------|
| Exact BNCentral config | Static Checker (id 114), no scroll | |
| Checker tiled + slow drift | Override hSpeed/vSpeed | |
| Different room's animated BG | Pick scrolling room | |
| (free-text) | Operator: "the background draw event might not be in the room files, but tied to an object within the room" | ✓ |

**Notes:** Discovery: `0051-bkdraw` object in BNCentral instance with `dxspeed=0.25, dyspeed=-1`. Default sprite `0064-BKA1` (32×32, 55 frames). bkdraw Create: image_speed=0.25.

### Port bkdraw confirmation

| Option | Description | Selected |
|--------|-------------|----------|
| Port bkdraw, BNCentral params | Tiled+scrolled+animated bg | ✓ |
| Static BKA1 tile (no scroll, no anim) | Lighter | |
| Static checker, defer to Phase 7 | Defer | |

### Scope reality-check

| Option | Description | Selected |
|--------|-------------|----------|
| Include in 6.1 (paired with D-46) | D-46 unbounded camera needs bg | ✓ |
| Defer bg to Phase 7 | Pure defect-fix scope | |
| Spawn Phase 6.2 | Separate phase | |

### Test coverage

| Option | Description | Selected |
|--------|-------------|----------|
| Mixed: unit + Playwright + UAT re-run | Layered | ✓ |
| Unit only | Skip Playwright | |
| Playwright only | Skip unit | |

---

## depth_set port

| Option | Description | Selected |
|--------|-------------|----------|
| Per-step player depth + tile depth init | Per-step on players, per-create on tiles | ✓ |
| Players only, tiles flat | Light | |
| Defer to Phase 7 | Skip | |

**Notes:** Operator: "the player character sprite depth (z index) never changes in the existing client. BNO has a fleshed out system for adjusting sprite depth on the fly". Discovery: `0354-depth_set.gml` formula + per-object usage.

### depth registry

| Option | Description | Selected |
|--------|-------------|----------|
| Per-entity in render code | Hardcoded layer/yOffset per renderer | ✓ |
| Shared registry table | Centralized | |
| Per-sprite meta.json field | Extractor-driven | |

---

## TSide1 — bottom-edge sides

| Option | Description | Selected |
|--------|-------------|----------|
| Bottom-edge sides only | Faithful — 5-8 GML only ships bottom | ✓ |
| All four sides | Uncomment legacy logic | |
| Defer to Phase 7 | Skip | |

**Notes:** Operator: "all floor tiles at the bottom edge of a path or group of floor tiles render 'sides' beneath them, to help give a sense of depth."

---

## NaviMask + collision + walkable-region

| Option | Description | Selected |
|--------|-------------|----------|
| NaviMask 18x7 feet bbox | Per 0034-NaviMask/meta.json | ✓ |
| Sprite bbox | Wrong | |
| Hardcoded 18x7 inline | Same numbers, no linkage | |

### Per-axis sub-pixel collision

| Option | Description | Selected |
|--------|-------------|----------|
| Per-axis sub-pixel collision | Per-pixel sub-step, separate X/Y | ✓ |
| Bounding-box sweep | Single-step | |
| Capsule/circle | Modern | |

### Fall-off-edge

| Option | Description | Selected |
|--------|-------------|----------|
| Defer to Phase 7 | Skip falls | |
| Port basic fall (no Abyss) | Lightweight | |
| Full port | Heavy | |
| (free-text) | Operator: "defer to phase 7. note that most (but not all) floor tile types have borders in logic which keep the player from falling off. ... in the original game, these per-tile borders were spawned in conditionally as objects, depending on the existence of neighboring floor tiles. but that's a very heavy solution to something that should be much simpler. need to modernize this." | ✓ |

**Notes:** Replace legacy per-tile-object border spawning with a modernized walkable-region mask derived from the room's tile grid at load. NaviMask bbox checked against this mask per axis. Edges block player; falls deferred.

---

## Claude's Discretion

- Wave / plan sequencing (within the 4-wave structure outlined in CONTEXT.md)
- D-40 root-cause investigation depth (read-only spike vs in-execute binary-search)
- Background scroll math details (bleed-tile count, fractional offset rendering)
- Aseprite round-trip verification depth for D-39
- Phaser depth math sign-check at implementation
- `c_aqua` / `c_white` exact color literal
- Background-renders-past-edges rendering mechanism (z-layer, scrollFactor, layered scene)

## Deferred Ideas

See CONTEXT.md `<deferred>` section. Highlights:
- Fall-off-edge logic + Digital_Abyss + duo-net (full Step.gml fall handling)
- Ice tiles, movement tiles, Hexport, JokerShell, slidedir sprite handoff
- All other rooms beyond mvp-lobby (PAR-03)
- Top/left/right tile borders (commented out in legacy 5-8)
- Multi-floor depth
- depth-registry consolidation
- 8-direction sprite variant handoff for diagonal Run/Stand
- Per-tile-object border-spawning (replaced by walkable-region mask)
- HexportMask + NaviCol secondary collision masks
- Aseprite round-trip UX polish
