# room-service

`room-service` owns Room Cell state, room snapshots, command log, durable room facts, Redis lease, and Tencent IM room message bridging.

## Direct IM UI

Room UI events are sent to Tencent IM directly after the Room Cell command commits. The direct IM lane is low latency and best-effort; it is not the owner of recoverable room state.

Direct-IM-only event types do not write `room_outbox` because snapshot or query APIs can repair missed display state:

- `RoomHeatChanged`: heat display; snapshot can repair missed values.
- `RoomRankChanged`: mic/rank display; direct IM display is preferred.
- `RoomRocketFuelChanged`: coalesced in memory and flushed directly to Tencent IM.
- `RoomRocketIgnited`: sent directly to Tencent IM; launch scheduling is still created after command commit.
- `RoomRocketLaunched`: launch display is sent directly to Tencent IM.

Durable facts with UI display are sent directly to Tencent IM after the Room Cell command commits, but they still write `room_outbox`:

- `RoomGiftSent`: room gift animation, public screen, and regional display use direct IM; statistics, activity, growth, task, and CP relation consumers still use durable outbox.
- `RoomUserJoined`: entry animation, vehicle, and online display use direct IM; active-user statistics still use durable outbox.
- `RoomUserLeft`: leave UI uses direct IM; recovery still comes from Room Cell command log and snapshot.
- `RoomMicChanged`: mic UI uses direct IM; mic duration consumers still use durable outbox.
- `RoomMicSeatLocked`, `RoomClosed`, `RoomUserKicked`, `RoomUserMuted`, `RoomUserUnbanned`, `RoomProfileUpdated`, `RoomBackgroundChanged`, `RoomChatEnabledChanged`, and `RoomAdminChanged`: UI updates use direct IM; state changes, side effects, audit, or notification recovery stay on command log/outbox.

## Durable Outbox

Business facts that downstream services must not miss still write `room_outbox` and are published by the outbox worker. Sending a display event directly to Tencent IM does not change command log, snapshot, room lease, wallet debit, or room state ownership.

The room outbox worker no longer publishes room UI IM. It only fans out durable facts to MQ/activity consumers. Gift sender balance is not included in room IM because room messages are broadcast to all room users. `SendGiftResponse.coin_balance_after` returns the sender's final COIN balance synchronously, including lucky-gift reward balance when a reward is granted.

`RoomRocketIgnited` keeps a reliability check outside direct IM: after the room command commits, room-service still schedules the delayed launch wakeup, and the launch worker revalidates rocket id, level, launch time, status, reset time, and room lease before creating `RoomRocketLaunched`.

Robot display events keep their original best-effort display publisher path in this pass.
