---
phase: 06.1-gap-closure-d-39-d-46-uat-2026-05-11
plan: 04
type: execute
wave: 2
depends_on: ["06.1-01", "06.1-02"]
files_modified:
  - apps/client/src/render/PlayerRenderer.ts
  - apps/client/src/render/SpriteStateMachine.ts
  - apps/client/src/render/Nameplate.ts
  - apps/client/index.html
  - apps/client/public/assets/fonts/FixedsysExcelsior.woff2
  - apps/client/src/__test__/sprite-state-rate.test.ts
  - apps/client/src/__test__/depth-set.test.ts
  - apps/client/src/__test__/nameplate-color.test.ts
autonomous: false  # WOFF2 fetch may require operator drop-in via checkpoint:human-action per checker B3
requirements: [REQ-CLI-04, REQ-CLI-08]
must_haves:
  truths:
    - "SpriteStateMachine.deriveFrame advances frame index by `framesPerTick` (= curspeed/10) per sim-tick, not per render-frame"
    - "PlayerRenderer.onSimulationTickLocal AND onSimulationTickRemote both call setDepth using depth_set(0,43) formula sign-flipped for Phaser"
    - "PlayerRenderer.ensureLocal signals sprite-recreation (so GameScene can re-bind camera follow)"
    - "Nameplate constructor accepts isLocal flag → cyan local (#00FFFF) / white remote (#FFFFFF) with 1px black drop shadow at (1,1) using Fixedsys Excelsior WOFF2"
    - "FixedsysExcelsior.woff2 served at /assets/fonts/ and registered via @font-face in index.html with font-display:block"
  artifacts:
    - path: "apps/client/src/render/SpriteStateMachine.ts"
      provides: "Fractional framesPerTick advance"
      contains: "framesPerTick"
    - path: "apps/client/src/render/PlayerRenderer.ts"
      provides: "Per-sim-tick depth_set + wasRecreated signal"
      contains: "wasRecreated"
    - path: "apps/client/src/render/Nameplate.ts"
      provides: "isLocal flag + cyan/white + drop shadow + Fixedsys font"
      contains: "isLocal"
    - path: "apps/client/public/assets/fonts/FixedsysExcelsior.woff2"
      provides: "Vendored CC0 Fixedsys Excelsior font binary"
      contains: ""
    - path: "apps/client/index.html"
      provides: "@font-face declaration"
      contains: "@font-face"
  key_links:
    - from: "GameScene sim-tick block"
      to: "PlayerRenderer.onSimulationTickLocal/Remote"
      via: "30 Hz callback gated by simTickAccumulator"
      pattern: "onSimulationTick(Local|Remote)"
    - from: "PlayerRenderer.ensureLocal"
      to: "GameScene cameraFollowApplied reset"
      via: "wasRecreated return-value field consumed by GameScene"
      pattern: "wasRecreated"
    - from: "Nameplate constructor"
      to: "Fixedsys WOFF2 via document fontFamily"
      via: "@font-face loaded before Phaser canvas-text renders"
      pattern: "Fixedsys Excelsior"
---

<objective>
Close D-41/D-42 (sim-tick anim rate canonical formula), D-45 (Fixedsys nameplate cyan/white + shadow + position), and the per-sim-tick depth_set port (D6.1-21/22). This plan owns the player-centric renderer surface: PlayerRenderer + SpriteStateMachine + Nameplate + Fixedsys font asset + @font-face wiring.

Purpose: Wave 3 GameScene wire-up calls into PlayerRenderer.ensureLocal and reads `wasRecreated` to drive the D-46 camera self-heal; it also needs Nameplate's `isLocal` flag in both ensureLocal and addRemote call sites. Wave 1 game-logic constants (`FRAMES_PER_TICK_AT_RUN`) are consumed here.

Output: PlayerRenderer carries per-tick depth + recreation signal; SpriteStateMachine advances fractionally; Nameplate cycles local cyan / remote white with Fixedsys + 1px shadow; font vendored; 3 client unit tests pin the new behaviour.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@CLAUDE.md
@.planning/PROJECT.md
@.planning/STATE.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-CONTEXT.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-RESEARCH.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-PATTERNS.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-VALIDATION.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-01-SUMMARY.md
@.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-02-SUMMARY.md

<interfaces>
<!-- From 06.1-02 (Wave 1) -->
import { RUN_SPEED_PX_PER_TICK, FRAMES_PER_TICK_AT_RUN, NAVI_MASK } from '@rebno/game-logic';
// FRAMES_PER_TICK_AT_RUN = 0.5

<!-- After this plan, downstream Wave 3 GameScene consumes: -->

// PlayerRenderer.ensureLocal signature change (option A per PATTERNS.md):
ensureLocal(args): { sprite: Phaser.GameObjects.Sprite; wasRecreated: boolean };

// SpriteStateMachine.deriveFrame signature change:
deriveFrame(
  vx: number, vy: number, lastFacing: Direction,
  cyclePhase: number, tickAccumulator: number,
  framesPerTick: number,   // NEW: caller passes FRAMES_PER_TICK_AT_RUN
): SpriteState;

// Nameplate constructor signature change:
new Nameplate(scene, username, x, y, domOverlay?, sessionId?, isLocal: boolean = false);
</interfaces>
</context>

<tasks>

<task type="auto" tdd="true">
  <name>Task 1: Wave 0 RED tests — sprite-state-rate, depth-set, nameplate-color</name>
  <files>apps/client/src/__test__/sprite-state-rate.test.ts, apps/client/src/__test__/depth-set.test.ts, apps/client/src/__test__/nameplate-color.test.ts</files>
  <read_first>
    - apps/client/src/__test__/sprite-state-machine.test.ts (existing analog — vitest setup; verify imports, fixture creation, mock Phaser scene)
    - apps/client/src/__test__/nameplate.test.ts (existing analog — Nameplate construction + DOM-mirror inspection)
    - apps/client/src/render/SpriteStateMachine.ts (current deriveFrame at lines 158-199; `TICKS_PER_FRAME_ADVANCE = 1` at line 28)
    - apps/client/src/render/PlayerRenderer.ts (current onSimulationTickLocal lines 128-158, onSimulationTickRemote lines 165-187; ensureLocal lines 76-97)
    - apps/client/src/render/Nameplate.ts (current text-creation at lines 42-49)
    - extracted/client-5-8/objects/0000-server/events/Other-7.gml line 4 (image_speed = curspeed/10)
    - extracted/client-5-8/objects/0000-server/events/Step.gml line 5 (`depth_set(0, 43)`)
    - extracted/client-5-8/scripts/0354-depth_set.gml (formula reference)
    - extracted/client-5-8/objects/0000-server/events/Draw.dnd.json (local: `font_color=c_aqua`)
    - extracted/client-5-8/objects/0042-player/events/Draw.dnd.json (remote: `font_color=c_white`)
    - .planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-VALIDATION.md lines 47-50 (test seed)
  </read_first>
  <behavior>
    - `sprite-state-rate.test.ts`: feed `deriveFrame` with vx=5, vy=0, cyclePhase=0, tickAccumulator=0, framesPerTick=0.5; loop 6 ticks; assert tickAccumulator wraps within [0,1) and cyclePhase advances by 3 (= 6 × 0.5). Run another assertion at framesPerTick=0.25 (BKA1 rate) confirming the same machinery is rate-parametric.
    - `depth-set.test.ts`: pure-function test for the depth_set formula. Given `layer=0, yOffset=43, y=200` and Phaser's higher-on-top semantic, the depth value should be `-(1000 * 0 - (200 + 43)) = 243`. Given `layer=2, yOffset=0, y=300` → `-(2000 - 300) = -1700` (lower than the player → drawn below → CORRECT for a tile at y=300 when the player feet are also at y=243-equivalent and the player must draw on top). Assert at least 3 reference values. The implementation lives inline in each renderer per D6.1-24 (no shared registry yet), so this test asserts the formula as a free function exported from PlayerRenderer (or a small local helper) — exporting helps the test; planner picks where to expose.
    - `nameplate-color.test.ts`: construct two Nameplate instances — one with `isLocal: true`, one with `isLocal: false`. Inspect the Phaser Text style (mocked Phaser or jsdom — check the existing nameplate.test.ts for the convention). Assert local style.color is `#00FFFF` (or `rgb(0, 255, 255)` depending on Phaser normalization) and remote is `#FFFFFF`. Assert shadow config `{offsetX:1, offsetY:1, color:'#000000', blur:0, fill:true}` is set in both.
    - All 3 tests tagged `// [unit->REQ-CLI-04]` (sprite-state-rate, depth-set) and `// [unit->REQ-CLI-08]` (nameplate-color since CLI-08 is the visual gate).
    - Tests RED initially.
  </behavior>
  <action>
    Create the 3 test files under `apps/client/src/__test__/`. Mirror the vitest fixture pattern from `sprite-state-machine.test.ts` and `nameplate.test.ts`. For `depth-set.test.ts`, if the depth formula is inlined in PlayerRenderer (per D6.1-24), export a small helper `computeDepth(layer: number, yOffset: number, y: number): number` from PlayerRenderer or a sibling `depth-helpers.ts` file so the test can import it pure-form. Planner-pick: define the helper inline in PlayerRenderer and re-export to keep all depth math in one file per D6.1-24. Tag each test file with the appropriate `// [<unit>->REQ-CLI-XX]` literal. DO NOT modify renderer sources in this task — Task 2 makes the tests GREEN.
  </action>
  <verify>
    <automated>pnpm --filter @rebno/client test src/__test__/sprite-state-rate.test.ts src/__test__/depth-set.test.ts src/__test__/nameplate-color.test.ts</automated>
  </verify>
  <acceptance_criteria>
    - 3 test files exist under `apps/client/src/__test__/` with the exact filenames listed.
    - `sprite-state-rate.test.ts` contains `framesPerTick` literal AND asserts cyclePhase advance with the 0.5 rate.
    - `depth-set.test.ts` contains literal value `243` (computed depth at layer=0, yOffset=43, y=200) AND at least 3 reference cases.
    - `nameplate-color.test.ts` contains both `00FFFF` (or `rgb(0, 255, 255)`) and `FFFFFF` (or `rgb(255, 255, 255)`).
    - Each file contains the appropriate `// [<unit>->REQ-...]` tag.
    - `pnpm --filter @rebno/client tsc --noEmit` exits 0.
    - `pnpm --filter @rebno/client test src/__test__/sprite-state-rate.test.ts src/__test__/depth-set.test.ts src/__test__/nameplate-color.test.ts` exits NON-ZERO (RED). RED captured.
  </acceptance_criteria>
  <done>3 Wave 0 client unit tests committed; RED captured; types compile.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: GREEN — fractional SpriteStateMachine + per-sim-tick depth_set on PlayerRenderer + ensureLocal recreation signal + Nameplate cyan/white + Fixedsys font + @font-face</name>
  <files>apps/client/src/render/SpriteStateMachine.ts, apps/client/src/render/PlayerRenderer.ts, apps/client/src/render/Nameplate.ts, apps/client/index.html, apps/client/public/assets/fonts/FixedsysExcelsior.woff2</files>
  <read_first>
    - apps/client/src/render/SpriteStateMachine.ts (current deriveFrame at lines 158-199 + `TICKS_PER_FRAME_ADVANCE` at line 28 + RUN_FRAME_COUNT + padPhase helper at line ~197)
    - apps/client/src/render/PlayerRenderer.ts (full file — focus on ensureLocal lines 76-97, onSimulationTickLocal 128-158, onSimulationTickRemote 165-187, addRemote 199-206)
    - apps/client/src/render/Nameplate.ts (full file — text creation, DOM-mirror, follow callback at lines 72-77)
    - apps/client/index.html (current head — see whether any `<style>` block exists)
    - .planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-PATTERNS.md §"SpriteStateMachine.ts" lines 505-543, §"PlayerRenderer.ts" lines 467-502, §"Nameplate.ts" lines 619-664
    - .planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-RESEARCH.md §Pattern 2 lines 211-227 (anim rate), §Pattern 6 lines 312-336 (depth formula sign), §Pattern 9 lines 398-436 (nameplate)
    - extracted/client-5-8/objects/0000-server/events/Step.gml line 5 (`depth_set(0, 43)`)
    - extracted/client-5-8/objects/0000-server/events/Other-7.gml line 4 (`image_speed = global.curspeed/10`)
    - extracted/client-5-8/objects/0000-server/events/Draw.dnd.json (font_color=c_aqua for local)
    - extracted/client-5-8/objects/0042-player/events/Draw.dnd.json (font_color=c_white for remote)
  </read_first>
  <behavior>
    - All 3 Wave 0 tests from Task 1 GREEN.
    - Existing `apps/client/src/__test__/sprite-state-machine.test.ts` and `apps/client/src/__test__/nameplate.test.ts` continue to pass (or are updated minimally with rationale).
    - SpriteStateMachine no longer exports `TICKS_PER_FRAME_ADVANCE`; instead `deriveFrame` accepts a `framesPerTick: number` parameter.
    - PlayerRenderer's `onSimulationTickLocal` AND `onSimulationTickRemote` call `setDepth?.(-(1000 * 0 - (y + 43)))` after `setFrame?.(...)`. Optional chaining preserved per S-06 (Phaser Rectangle fallback).
    - PlayerRenderer.ensureLocal returns `{ sprite, wasRecreated: boolean }` per option A in PATTERNS.md. All in-file callers of `ensureLocal` updated.
    - Nameplate constructor takes a final `isLocal: boolean = false` parameter; uses `'"Fixedsys Excelsior", monospace'` fontFamily; color `#00FFFF` when local else `#FFFFFF`; shadow `{offsetX:1, offsetY:1, color:'#000000', blur:0, fill:true}`. The DOM-mirror element retains `data-nameplate` attribute for Playwright.
    - PlayerRenderer's `ensureLocal` call to `new Nameplate(...)` passes `isLocal: true`; `addRemote` call passes `isLocal: false`.
    - `apps/client/index.html` <head> contains a `<style>` block declaring `@font-face` for `Fixedsys Excelsior` pointing at `/assets/fonts/FixedsysExcelsior.woff2` with `font-display: block`.
    - `apps/client/public/assets/fonts/FixedsysExcelsior.woff2` is committed as a vendored CC0 binary.
    - `__rebno.localFrame` continues to be set inside DEV/test mode so e2e tests can read it (S-08 preserved).
  </behavior>
  <action>
    Edit `apps/client/src/render/SpriteStateMachine.ts`: remove `export const TICKS_PER_FRAME_ADVANCE = 1`. Change `deriveFrame` signature to take a 6th parameter `framesPerTick: number`. Replace the integer-accumulator logic with fractional advance: `const advanced = tickAccumulator + framesPerTick; const wholeFrames = Math.floor(advanced); const newAccumulator = advanced - wholeFrames; const newPhase = (cyclePhase + wholeFrames) % RUN_FRAME_COUNT[facing]`. Return updated `{ facing, cyclePhase: newPhase, tickAccumulator: newAccumulator, isRunning, frameKey: ... }`. Tag `// [impl->REQ-CLI-04]`.

    Edit `apps/client/src/render/PlayerRenderer.ts`: import `FRAMES_PER_TICK_AT_RUN` from `@rebno/game-logic`. Update both `onSimulationTickLocal` and `onSimulationTickRemote` to pass `FRAMES_PER_TICK_AT_RUN` as the 6th arg to `deriveFrame`. After `setFrame?.(state.frameKey)` and `setPosition(x, y)`, insert depth-set per D6.1-21/22: define inline `const PLAYER_LAYER = 0; const PLAYER_Y_OFFSET = 43;` (with SOURCE comment citing `0000-server/Step.gml:5`); compute `const depth = -(1000 * PLAYER_LAYER - (y + PLAYER_Y_OFFSET));` and call `sprite.setDepth?.(depth)`. Export a small helper `export function computeDepth(layer: number, yOffset: number, y: number): number { return -(1000 * layer - (y + yOffset)); }` so the depth-set test can import it pure.

    Modify `ensureLocal` to return `{ sprite, wasRecreated }`. When the existing local sprite is present, return `{ sprite: this.local.sprite, wasRecreated: false }`. When creating a new sprite, return `{ sprite, wasRecreated: true }`. Update all in-file consumers of `ensureLocal` to destructure or read the field — GameScene (Wave 3) will read `wasRecreated` to reset its `cameraFollowApplied` flag.

    Update PlayerRenderer's call to `new Nameplate(...)` in `ensureLocal` to pass `isLocal: true`. Update the call in `addRemote` to pass `isLocal: false`. Tag PlayerRenderer file header `// [impl->REQ-CLI-04] [impl->REQ-CLI-08]`.

    Edit `apps/client/src/render/Nameplate.ts`: extend constructor with a final `isLocal: boolean = false` parameter (default false for backwards safety). Inside, pass to Phaser `scene.add.text`: `fontFamily: '"Fixedsys Excelsior", monospace'`, `fontSize: '16px'`, `color: isLocal ? '#00FFFF' : '#FFFFFF'`, `shadow: { offsetX: 1, offsetY: 1, color: '#000000', blur: 0, fill: true }`. Keep the DOM-mirror element + `data-nameplate` attribute intact. Preserve the follow() position math (`spriteY - spriteHeight - NAMETAG_OFFSET_Y`); only verify it produces `y - 16` equivalent given sprite origin (0.5, 1). Tag `// [impl->REQ-CLI-08]`.

    Edit `apps/client/index.html`: add to `<head>` a `<style>` block:
    `@font-face { font-family: 'Fixedsys Excelsior'; src: url('/assets/fonts/FixedsysExcelsior.woff2') format('woff2'); font-display: block; }`. Tag with HTML comment `<!-- [impl->REQ-CLI-08] -->`.

    Vendor `FixedsysExcelsior.woff2` to `apps/client/public/assets/fonts/` per D6.1-10 (WOFF2 is the LOCKED delivered state — monospace is a runtime fallback only, NOT an acceptable build artifact per checker B3).

    Deterministic source (planner-picked): vendor the CC0 public-domain Fixedsys Excelsior 3.01 release from `https://github.com/kika/fixedsys` (repository "fixedsys" by kika). Preferred path: download the upstream TTF (`FSEX300.ttf`) from `https://github.com/kika/fixedsys/raw/master/FSEX300.ttf`, then convert TTF→WOFF2 locally via `woff2_compress FSEX300.ttf` (Google `woff2` CLI; installable via `apt-get install woff2` on Debian/Ubuntu, `brew install woff2` on macOS, or vendored Windows binary from `https://github.com/google/woff2/releases`). Rename the resulting `FSEX300.woff2` to `FixedsysExcelsior.woff2` and commit at `apps/client/public/assets/fonts/FixedsysExcelsior.woff2`. Expected sha256: record the actual hash in the 06.1-04-SUMMARY.md `font_provenance` section at commit-time (pin reproducibility).

    If `woff2_compress` is unavailable in the execution environment OR the upstream binary cannot be fetched non-interactively, **the task BLOCKS** with `autonomous: false` checkpoint (same shape as Plan 06.1-07 Task 2). The operator drops `FixedsysExcelsior.woff2` (≥10 KB) at the path, types "resume", and the task continues. No monospace-fallback build artifact is acceptable per D6.1-10 lock.

    Add a sibling `LICENSE.txt` (or `README.md`) under `apps/client/public/assets/fonts/` citing the CC0 dedication and upstream URL.

    Run `pnpm trace:check` after edits.
  </action>
  <verify>
    <automated>pnpm --filter @rebno/client test src/__test__/sprite-state-rate.test.ts src/__test__/depth-set.test.ts src/__test__/nameplate-color.test.ts src/__test__/sprite-state-machine.test.ts src/__test__/nameplate.test.ts &amp;&amp; pnpm --filter @rebno/client tsc --noEmit &amp;&amp; pnpm trace:check</automated>
  </verify>
  <acceptance_criteria>
    - All 3 Wave 0 tests GREEN.
    - Existing `sprite-state-machine.test.ts` and `nameplate.test.ts` continue to pass (or have minimal documented updates).
    - `grep -v '^#' apps/client/src/render/SpriteStateMachine.ts | grep -c 'TICKS_PER_FRAME_ADVANCE'` = 0 (constant removed).
    - `grep -v '^#' apps/client/src/render/SpriteStateMachine.ts | grep -c 'framesPerTick'` ≥ 1.
    - `grep -v '^#' apps/client/src/render/PlayerRenderer.ts | grep -c 'wasRecreated'` ≥ 2 (set on creation, returned on existing).
    - `grep -v '^#' apps/client/src/render/PlayerRenderer.ts | grep -c 'setDepth?\\.'` ≥ 2 (local + remote).
    - `grep -v '^#' apps/client/src/render/PlayerRenderer.ts | grep -c 'FRAMES_PER_TICK_AT_RUN'` ≥ 1 (imported and passed).
    - `grep -v '^#' apps/client/src/render/PlayerRenderer.ts | grep -c 'computeDepth'` ≥ 1 (exported helper).
    - `grep -v '^#' apps/client/src/render/Nameplate.ts | grep -c 'isLocal'` ≥ 2 (param + usage).
    - `grep -v '^#' apps/client/src/render/Nameplate.ts | grep -c 'Fixedsys Excelsior'` ≥ 1.
    - `grep -c '@font-face' apps/client/index.html` ≥ 1.
    - `test -f apps/client/public/assets/fonts/FixedsysExcelsior.woff2 && [ "$(wc -c < apps/client/public/assets/fonts/FixedsysExcelsior.woff2)" -gt 10000 ]` exits 0 — file present AND non-trivial size (>10 KB) per checker B3. Monospace fallback is NOT an acceptable build artifact.
    - sha256 of the committed WOFF2 is recorded in `06.1-04-SUMMARY.md` under a `font_provenance` section.
    - `grep -q "FixedsysExcelsior" apps/client/index.html || grep -rq "FixedsysExcelsior" apps/client/src/` exits 0 per checker I2 — `@font-face` wire-up confirmed in index.html OR a CSS file under src/. (Vite serves `public/` statically; no build config change required.)
    - `pnpm trace:check` exits 0.
  </acceptance_criteria>
  <done>SpriteStateMachine fractional; PlayerRenderer per-sim-tick depth + recreation signal; Nameplate cyan/white/shadow + Fixedsys; @font-face + woff2 in place; 3 Wave 0 tests GREEN; existing tests preserved; trace clean.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| vendored woff2 font asset | Static asset served over TLS; no executable code |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06.1.04-01 | Tampering | font file in transit | accept | Asset served over TLS; if a malicious font were substituted, browser font sandbox limits impact to glyph rendering |
| T-06.1.04-02 | Information disclosure | DOM-mirror nameplate element exposes usernames | accept | Pre-existing behavior; nameplates are already broadcast to all room participants by server |
</threat_model>

<verification>
- `pnpm --filter @rebno/client test` exits 0 (all unit tests pass).
- `pnpm --filter @rebno/client tsc --noEmit` exits 0.
- `pnpm trace:check` exits 0.
- Manual visual check (deferred to UAT): Fixedsys font renders crisply at 16px; local nameplate cyan; remote nameplate white; 1px black shadow visible.
</verification>

<success_criteria>
- Fractional frame advance per sim-tick replaces boolean integer advance.
- Per-sim-tick `setDepth` fires from both local and remote sim-tick callbacks.
- `wasRecreated` signal exists on `ensureLocal` so Wave 3 GameScene can detect sprite recreation.
- Nameplate matches BNO local-cyan/remote-white rule with Fixedsys + operator-requested shadow.
- `@font-face` registered with `font-display: block` so Phaser canvas-text picks up the font before first render.
</success_criteria>

<output>
After completion, create `.planning/phases/06.1-gap-closure-d-39-d-46-uat-2026-05-11/06.1-04-SUMMARY.md`: SpriteStateMachine signature change call sites updated, PlayerRenderer depth-set insertion points + computeDepth export location, Nameplate isLocal call sites in PlayerRenderer, font binary provenance + license note.
</output>
