<!-- [doc->REQ-CLI-04] [doc->REQ-CLI-08] -->

# 06.2-04 — Spawn-Delay Legacy Spec (D-53)

**Phase:** 06.2-gap-closure-d-50-d-53-uat-2026-05-13
**Plan:** 04 (read-only research; legacy source-of-truth extraction)
**Consumed by:** 06.2-10-PLAN.md `spawnDelayTicks` implementation
**Hard Rule referenced:** CLAUDE.md Rule #6 — *Extract → document → rewrite, in that order.*

This document is the canonical source for D-53 ("hold player still after spawn before run animation can begin"). 06.2-10 MUST cite this file and treat its numeric value (`30` ticks) and sprite state (south-facing stand) as load-bearing. The Option A vs Option B decision is locked in §5 below — 06.2-10 does NOT re-litigate it.

---

## 1. Source files inspected

All paths relative to repo root. Line numbers are 1-indexed (cat -n equivalent), matching the extracted GML files in revision 5-8 (`legacy/open-source-release/BN Online Client 5-8.gmd`).

| File | Lines relevant to spawn hold |
|---|---|
| `extracted/client-5-8/objects/0042-player/events/Create.gml` | L2 (`image_speed = 0.8;`) — default sprite playback rate, NOT a spawn gate |
| `extracted/client-5-8/objects/0042-player/events/Other-7.gml` | L2..L9 active branch (TeleIn/JoinIn → NaviStandD swap); **L7..L8 commented-out `teledin = 1; alarm[0] = 30;`**; L19..L30 a larger commented-out alternate JoinIn branch with the same `teledin = 1; alarm[0] = 30;` (L28..L29) |
| `extracted/client-5-8/objects/0042-player/events/Alarm.gml` | L2 (`teledin = 0;`) — the only active line; resets the gate the (commented) alarm would set |
| `extracted/client-5-8/objects/0042-player/events/Step.gml` | L46..L63 sprite-write block; L51 is a commented predicate that referenced `teledin` to gate `sprite_index = global.p_spr[pid,0]` |
| `extracted/client-5-8/objects/0042-player/events/Draw.gml` | L8 active branch excludes TeleIn/JoinIn from normal name-draw; L20..L22 active branch draws `JoinInR` as an overlay during JoinIn |

GameMaker 5.3a event-type note: event `Other-7` is conventionally **Room Start** in this era. Confirming against `decomp/wiki/05-events.md` is recommended but not strictly required for this spec — the only fact the implementation cares about is that this event runs once on spawn/room-enter, which is consistent with the surrounding code (it dispatches on the just-set `sprite_index` from the spawn entry sprite). See `decomp/wiki/05-events.md` ("Other" event family) if disambiguation is later needed.

---

## 2. Verbatim citations

Active code (post-comment-stripping):

`Create.gml:2` — `image_speed = 0.8;`

`Other-7.gml:2..6` (entered when the spawn entry sprite is TeleIn or JoinIn):
```gml
if(sprite_index == TeleIn || sprite_index == JoinIn)
{
image_speed = 0.4;
global.p_spr[pid,0] = NaviStandD;
sprite_index = NaviStandD;
```

`Other-7.gml:7..8` (the load-bearing commented block — preserved in the latest revision, NOT deleted):
```gml
/*teledin = 1;
alarm[0] = 30;*/
```

`Alarm.gml:2` — `teledin = 0;`

`Step.gml:51` (also commented, but documents the original `teledin` gate intent on the sprite write):
```gml
/*if((global.p_spr[pid,0] == TeleIn && !teledin) || (global.p_spr[pid,0] == JoinIn && !teledin) || global.p_spr[pid,0] != TeleIn)
```

---

## 3. Canonical legacy behavior — what actually happens in revision 5-8

When a player enters a room in revision 5-8 (the latest, authoritative client):

1. **Create event** (`Create.gml:2`) fires once on instance construction, setting `image_speed = 0.8`. This is the **default** sprite playback rate for the player object; it is NOT a spawn-specific delay. It does NOT pin the sprite to any particular frame.
2. **Room Start / Other-7 event** (`Other-7.gml:2..6`) fires immediately after Create, inspects the incoming `sprite_index`, and — when the entry sprite is `TeleIn` or `JoinIn` — does three things:
   - sets `image_speed = 0.4` (slows playback to ~40% of default),
   - writes `NaviStandD` (south-facing stand) into the shared registry `global.p_spr[pid,0]`,
   - overwrites the local `sprite_index` to `NaviStandD`.
3. **Lines `Other-7.gml:7..8`** — `teledin = 1; alarm[0] = 30;` — are **COMMENTED OUT**. They are preserved verbatim in revision 5-8 (not deleted; the `/*...*/` braces are explicit in the file, visible in `cat`/Read output), but they do not execute.
4. **Alarm event** (`Alarm.gml:2`) contains only `teledin = 0;`. Since no active code path arms `alarm[0]` anywhere in the 5-8 player object, **this alarm event effectively never fires in revision 5-8**. It is dead code.
5. **Step event** holds a related commented predicate at `Step.gml:51` that would, if active, have gated the `sprite_index = global.p_spr[pid,0]` write on `!teledin` (i.e. would have skipped writing the sprite while `teledin` was 1).

**Net legacy effect in 5-8:** on spawn, the sprite is swapped to NaviStandD, image_speed is set to 0.4, and there is **no input/sprite gate** — the player can theoretically move on the very next step. Any operator-perceived "hold" comes from network/UI latency, not from an explicit timer.

**Net legacy effect in the prior revision (commented but preserved):** `teledin = 1` would have been set on spawn, `alarm[0] = 30` armed, and 30 game-steps later the Alarm event would have cleared `teledin` back to 0. During those 30 steps, the Step-event `teledin` gate (also commented in 5-8, see `Step.gml:51`) would have prevented `sprite_index` from being overwritten by the per-frame walk/stand logic — locking the player visually to `NaviStandD` for the duration.

---

## 4. Tick-rate conversion

Room speed: **30 steps/sec** (locked in CLAUDE.md "Extracted Constants" table, also in `extracted/client-5-8/rooms/*/meta.json` `speed: 30`).

`alarm[0] = 30` schedules the alarm to fire 30 steps after being armed.

Conversion:
- 30 steps × (1 tick / 1 step at 30 Hz) = **30 ticks**
- 30 ticks / 30 Hz = **1.000 s**

---

## 5. Decision matrix for 06.2-10

### Option A — Port the commented-out alarm (operator-intent reading)

- **Hold duration:** 30 steps = 30 ticks = 1.000 s
- **Sprite state during hold:** `NaviStandD` (south-facing stand) — derived from `Other-7.gml:5..6`
- **Input behavior during hold:** ignored (no movement applied locally) — derived from the absence of the Step-event walk update while `teledin == 1`
- **Position behavior during hold:** position MUST continue to update from server reconcile messages (we are not the authoritative source for `x`/`y` — see Hard Rule #1). Only the local input-driven movement is suppressed.

### Option B — Port only the active JoinIn → NaviStandD transition (literal-reading)

- **Hold duration:** 0 — no explicit timer
- **Sprite state during hold:** N/A (just a one-step swap to NaviStandD)
- **Input behavior:** no artificial gate; any "delay" perception in legacy gameplay would have come from the JoinIn entry-animation playing at `image_speed=0.4`, which the REBNO rebuild does not yet honor.

---

## 6. Recommendation — **Option A (LOCKED)**

06.2-10 MUST implement Option A. Rationale:

1. **Operator UAT explicitly flagged D-53** as a hold before the run animation can begin — Option B does not satisfy the request.
2. **The commented block is preserved verbatim in revision 5-8** (`Other-7.gml:7..8`), not deleted. Original author kept it as documentation of intended behavior; this is the strongest signal of canonical intent we have absent a design doc.
3. **It is the only numeric duration in any spawn-adjacent legacy code.** No other extraction-derived value competes.
4. **Hard Rule #4** (CLAUDE.md): formats and behaviors must be extraction-derived. The commented `alarm[0] = 30` IS the extraction-derived value; there is no other source of truth to consult.
5. **Hard Rule #6** (CLAUDE.md): extract → document → rewrite. This document closes the "document" stage so 06.2-10 can be a pure port.

---

## 7. Implementation contract for 06.2-10

The following MUST hold in the 06.2-10 implementation. 06.2-10 cites this section verbatim.

**Initial state (PlayerRenderer.local construction):**
- `spawnDelayTicks = 30` (integer, in ticks @ 30 Hz)

**Per-tick local loop (`onSimulationTickLocal` or equivalent — 06.2-10 picks the exact method name):**

While `spawnDelayTicks > 0`:
1. Decrement `spawnDelayTicks` by 1 at the start of the tick.
2. Pin the rendered sprite to `deriveFrame(intendedDx=0, intendedDy=0, facing='down', ...)` — the south-facing stand. This is the NaviStandD-equivalent in REBNO's frame derivation function.
3. **Ignore local input** for the purposes of computing the intended dx/dy that would otherwise drive the walk animation and the optimistic local-position update.
4. **Continue to apply server-reconciled position updates** to `x`/`y`. Server-authoritative state is never gated by `spawnDelayTicks` (Hard Rule #1).
5. **Continue to send the local input intent to the server** (do not gate intent transmission — server is authoritative on what intent does). The local gate is purely about local animation and optimistic prediction.

When `spawnDelayTicks === 0`:
- Normal `deriveFrame` logic resumes (facing derived from input, walk/stand picked from intended dx/dy).
- Normal optimistic local-position update resumes.

**Reset semantics:** `spawnDelayTicks` is reset to `30` whenever the local player is (re)constructed — i.e. on first room entry and on any future respawn flow that constructs a fresh `PlayerRenderer.local`. Reconnect-to-existing-session is out-of-scope for D-53; 06.2-10 may treat reconnect as a fresh construction.

---

## 8. REQ-CLI-04 / REQ-CLI-08 traceability

This document closes the `doc` stage for the spawn-delay portion of:
- **REQ-CLI-04** — Client renders walk animation faithfully (the spawn-hold-then-walk transition is a sub-fidelity of the walk-animation contract).
- **REQ-CLI-08** — CLI-08 hard milestone: two players move + chat over deployed server. The spawn-hold is a visible-on-screen behavior that the UAT acceptance criteria for CLI-08 reference.

`impl` stage will be closed by 06.2-10. `unit` stage closure (if `required_stages` for these reqs includes `unit`) is the responsibility of 06.2-10's test task.

---

*End of spec. Total citations of `extracted/client-5-8/objects/0042-player/events/`: see §1 table (5 distinct files cited) + verbatim citations in §2.*
