---
name: per-tick-log-freeze-pattern
description: Per-tick log.info in REBNO game tickLoop simulation step (Colyseus) causes main-loop stall + accumulator burst-replay freeze. Strip spike telemetry from hot paths before merging.
metadata: 
  node_type: memory
  type: feedback
  originSessionId: bb7d12df-3d7d-4a5e-853e-2752958e15f1
---

Any per-tick `log.info` inside `RebnoRoom.tickLoop` simulation step (`apps/server/src/RebnoRoom.ts`) must be removed or sampled before merging — even when guarded by `account_id === 'uat_*'`. Spike telemetry from a debug plan left in main = production hazard.

**Why:** 2026-05-16 dual-tab UAT freeze-then-snap diagnosed @ `.planning/debug/server-perf-freeze.md`. Per-tick `log.info({event:'d58c_player_axes',...})` from Plan 06.4-10 D-58c spike fired at 40Hz (2 players × 30Hz tick + axis-broadcast). On Fly shared-cpu-2x with `LOG_LEVEL=debug` and dual OTLP log pipeline (SDK `BatchLogRecordProcessor` + `pino-opentelemetry-transport` both shipping `/v1/logs`), worker-thread + OTLP backpressure stalled the main event loop 1-4s. `advanceAccumulator` then replayed 20-80 missed ticks as a single burst → visual freeze-then-snap. Fix: commit `29f1858` deleted the per-tick log, deduped OTLP pipeline, dropped `LOG_LEVEL` to `info`.

**How to apply:**
- When reviewing a plan that adds telemetry inside `tickLoop` (30Hz) or any hot per-frame path, require: (a) sample by tick-mod-N, (b) gate by `LOG_LEVEL` check, or (c) explicit removal commit before phase close.
- Never run two OTLP log pipelines against the same `/v1/logs` sink. Pick one: SDK-side `BatchLogRecordProcessor` OR pino transport (`pino-opentelemetry-transport`). Project uses pino transport — `otel-init.ts` must NOT register `logRecordProcessor`.
- `tick_loop_slow` warn now lives in `RebnoRoom.tickLoop` (realDt>100ms OR ticks>4). Future stall regressions will surface there — check this signal before deep-diving freeze reports.

See [[worktree-merge-cwd-gotcha]] for another operational gotcha pattern.
