---
phase: 06.3
plan: "05"
type: execute
wave: 1
depends_on: []
files_modified:
  - apps/client/src/render/PlayerRenderer.ts
autonomous: true
requirements:
  - REQ-CLI-04
  - REQ-CLI-08
required_stages:
  - impl

must_haves:
  truths:
    - "window.__rebno.spawnDelayTicks and spawnDelayTicksTotal are published unconditionally after each sim-tick decrement"
    - "Operator can read the countdown from devtools: spawnDelayTicks starts at 30 and counts down to 0"
    - "ensureLocal reinit audit result is documented in the plan summary: which code path skips reinit on reconnect"
  artifacts:
    - path: apps/client/src/render/PlayerRenderer.ts
      provides: "window.__rebno.spawnDelayTicks publish in onSimulationTickLocal(); ensureLocal reinit audit notes in summary"
      contains: "spawnDelayTicks"
  key_links:
    - from: apps/client/src/render/PlayerRenderer.ts
      to: window.__rebno
      via: "unconditional globalThis spread after spawnDelayTicks decrement"
      pattern: "spawnDelayTicks"
---

<objective>
Spike: D-53 (a) publish spawnDelayTicks to window.__rebno + (b) ensureLocal reinit audit.

Two parts, single small plan:
(a) Add window.__rebno.spawnDelayTicks publication after each decrement in onSimulationTickLocal(). Operator reads the countdown from devtools to confirm the 30-tick hold is working.
(b) Audit the ensureLocal() reinit path — trace the reconnect/re-entry path to confirm whether spawnDelayTicks is reset to 30 on each fresh local spawn (including reconnect-after-evict). RESEARCH.md confirms the reinit bug: ensureLocal() returns early if `this.local` exists (idempotent guard), so spawnDelayTicks is NOT reset on WS reconnect. Document findings; the fix is in plan 06.3-16.

Purpose: Make spawnDelayTicks operator-visible (part a) and confirm the reinit bug path (part b) before plan 06.3-16 implements the fix.

Output: window.__rebno.spawnDelayTicks readable from devtools + reinit audit documented in summary.
</objective>

<execution_context>
@C:\Users\decid\.claude\get-shit-done\workflows\execute-plan.md
@C:\Users\decid\.claude\get-shit-done\templates\summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/phases/06.3-cycle-4-gap-closure-d-40-d-45-d-51-d-53-d-54-d-55-d-56-d-57-/06.3-CONTEXT.md
@.planning/phases/06.3-cycle-4-gap-closure-d-40-d-45-d-51-d-53-d-54-d-55-d-56-d-57-/06.3-RESEARCH.md

<interfaces>
<!-- Key references from RESEARCH.md verified sections -->

From apps/client/src/render/PlayerRenderer.ts:
  ensureLocal() at line 127: if (this.local) return { sprite, wasRecreated: false } — idempotent early return
  onSimulationTickLocal() at lines 196-231:
    - Decrements this.local.spawnDelayTicks per tick (around line 196-197)
    - local state has field .spawnDelayTicks: number
  this.local.spawnDelayTicks: number initialized to 30 in ensureLocal() line 147

Reconnect bug path (RESEARCH confirmed):
  - GameScene creates new PlayerRenderer on each create() call → this.local starts undefined → ensureLocal() initializes spawnDelayTicks=30 ✅
  - But: reconnect path (WS reconnect via reconnectMachine, same GameScene instance) → PlayerRenderer persists → onLocalJoin() calls ensureLocal() → this.local NOT undefined → early return → spawnDelayTicks NOT reset ❌

From apps/client/src/scenes/GameScene.ts:
  GameScene.create() at line 168-170: this.playerRenderer = new PlayerRenderer(...)
  GameScene onLocalJoin at line 468: this.playerRenderer?.ensureLocal(...)

Unconditional __rebno pattern from reconciler.ts lines 68-78:
  if (typeof globalThis !== 'undefined') {
    const g = globalThis as any;
    g.__rebno = { ...(g.__rebno ?? {}), fieldName: value };
  }
  RULE: NO if (import.meta.env.DEV) gate.
</interfaces>
</context>

<tasks>

<task type="auto">
  <name>Task 1: Publish spawnDelayTicks to window.__rebno in onSimulationTickLocal()</name>
  <files>apps/client/src/render/PlayerRenderer.ts</files>
  <read_first>
    - apps/client/src/render/PlayerRenderer.ts lines 190-235 (onSimulationTickLocal — find the spawnDelayTicks decrement)
    - apps/client/src/render/PlayerRenderer.ts lines 120-155 (ensureLocal — the early-return idempotency guard, spawnDelayTicks initialization)
    - .planning/phases/06.3-cycle-4-gap-closure-d-40-d-45-d-51-d-53-d-54-d-55-d-56-d-57-/06.3-PATTERNS.md §"D-53 (a) — add spawnDelayTicks publication"
    - .planning/phases/06.3-cycle-4-gap-closure-d-40-d-45-d-51-d-53-d-54-d-55-d-56-d-57-/06.3-RESEARCH.md §"D-53 — SpawnDelayTicks operator-visibility"
  </read_first>
  <action>
In `onSimulationTickLocal()`, immediately after the `this.local.spawnDelayTicks -= 1` decrement (or equivalent), add:

```typescript
// D-53 (a): publish spawn delay counter to window.__rebno for operator devtools read.
// Unconditional — no DEV gate (RC1 proved env-gated hooks silent on staging).
// [impl->REQ-CLI-04] [impl->REQ-CLI-08]
if (typeof globalThis !== 'undefined') {
  const g = globalThis as any;
  g.__rebno = {
    ...(g.__rebno ?? {}),
    spawnDelayTicks: this.local.spawnDelayTicks,
    spawnDelayTicksTotal: 30,
  };
}
```

Also audit the ensureLocal() reinit path (READ-ONLY — do NOT fix here; fix is in 06.3-16):
Read lines 120-155 carefully. Confirm:
1. Line 127 has: `if (this.local) return { sprite: this.local.sprite, wasRecreated: false }`
2. spawnDelayTicks is initialized on line 147 (or nearby) as: `spawnDelayTicks: 30`
3. The reconnect code path: GameScene.ts ~line 468 calls ensureLocal() without recreating PlayerRenderer

Document findings in the SUMMARY.md (do NOT add reinit fix code here — that's plan 06.3-16):
- Confirm the reconnect path creates a NEW PlayerRenderer instance OR reuses the existing one
- If reused: spawnDelayTicks not reset → D-53 bug confirmed → fix in 06.3-16
- If new instance: spawnDelayTicks reset correctly → D-53 is different bug → flag in summary for 06.3-16 to address
  </action>
  <verify>
    <automated>pnpm --filter @rebno/client test --run</automated>
  </verify>
  <acceptance_criteria>
    - window.__rebno.spawnDelayTicks publish present after spawnDelayTicks decrement in onSimulationTickLocal()
    - No DEV gate on __rebno write
    - window.__rebno.spawnDelayTicksTotal = 30 (constant)
    - Client tests pass
    - Summary documents reconnect path reinit bug (whether bug exists via new-instance or not)
  </acceptance_criteria>
  <done>Operator can read window.__rebno.spawnDelayTicks from staging devtools; countdown from 30 to 0 visible during spawn-in hold. Reinit audit finding captured in summary for 06.3-16.</done>
</task>

<task type="auto">
  <name>Task 2: Commit spike</name>
  <files>apps/client/src/render/PlayerRenderer.ts</files>
  <read_first>
    - CLAUDE.md §"Atomic commits"
  </read_first>
  <action>
```
gsd-sdk query commit "spike(06.3-05): D-53 spawnDelayTicks window.__rebno publish + ensureLocal reinit audit [REQ-CLI-04] [REQ-CLI-08]" --files apps/client/src/render/PlayerRenderer.ts
```
  </action>
  <verify>
    <automated>git log --oneline -1</automated>
  </verify>
  <acceptance_criteria>
    - Commit references 06.3-05 and REQ-CLI-04/REQ-CLI-08
  </acceptance_criteria>
  <done>Spike committed</done>
</task>

</tasks>

<output_contract>
<!-- Consumed by 06.3-16 (D-53 ensureLocal reinit fix) -->
Findings this spike MUST document in the SUMMARY.md:
- `window.__rebno.spawnDelayTicks` field name and publication point (confirmed)
- ensureLocal() line number of idempotent early return
- Confirmation of which reconnect path skips reinit:
  - Path A: WS reconnect within same GameScene instance → PlayerRenderer persists → spawnDelayTicks not reset (RESEARCH says this is the bug)
  - Path B: Force-reset evict → LoginScene → new GameScene → new PlayerRenderer → no bug on this path
- Whether `resetSpawnDelay()` method approach (add to PlayerRenderer, call from GameScene.onLocalJoin) is correct fix path

Decision passed to 06.3-16:
- If Path A confirmed: add `resetSpawnDelay()` method to PlayerRenderer; call from GameScene.onLocalJoin() on every join
- Test coverage target: `spawn → simulate reconnect (WS reconnect, no new PlayerRenderer) → assert spawnDelayTicks === 30`
</output_contract>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Client → window.__rebno | Ephemeral diagnostic; no auth surface |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-06.3-05-spike | Info | window.__rebno spawnDelayTicks | accept | Field contains only a tick counter (integer 0-30). No PII, no auth state. |

No auth/session/data-flow surface touched — no threats this plan.
</threat_model>

<verification>
1. Deploy to staging
2. Login uat_a
3. Open devtools console
4. Immediately after login: `window.__rebno.spawnDelayTicks` — expect value between 0 and 30 (counting down)
5. Wait 1 second: `window.__rebno.spawnDelayTicks` — expect 0 (hold complete)
</verification>

<success_criteria>
- window.__rebno.spawnDelayTicks counts down from 30 to 0 over ~1 second post-login
- Client tests pass
- Reinit audit documented in summary
</success_criteria>

<output>
After completion, create `.planning/phases/06.3-cycle-4-gap-closure-d-40-d-45-d-51-d-53-d-54-d-55-d-56-d-57-/06.3-05-SUMMARY.md`
</output>
