# 语音房火箭功能架构

## 服务边界

- `room-service` 是火箭进度、点火、待发射队列和奖励结算 owner。
- `gateway-service` 只提供 App HTTP 入口和鉴权后的协议转换；`GET /api/v1/rooms/{room_id}/rocket` 会按奖励 `resource_group_id` 查询 wallet-service 并嵌入 `resource_group.items`。
- `server/admin` 只读写 `room-service` 内部配置接口，不直连 room 数据库。
- `wallet-service` 负责资源组发放。
- `activity-service` 负责区域/全局飘屏和带用户资料的奖励房间 IM。
- 腾讯云 IM 负责房间群、区域群和全局群投递。

## 状态模型

`RoomRocketState` 保存当前正在累计的火箭和已点火待发射队列。

| 字段 | 说明 |
| --- | --- |
| `current_level` | 当前正在累计的等级，最高 5 |
| `current_fuel` | 当前等级燃料 |
| `fuel_threshold` | 当前等级阈值 |
| `status` | `charging` 或 `completed` |
| `rocket_id` | 当前正在累计的火箭 ID |
| `pending_launches` | 已点火、等待延迟发射的火箭 |
| `last_rewards` | 最近一次发射奖励结果 |

`pending_launches` 中每一项锁定：

| 字段 | 说明 |
| --- | --- |
| `rocket_id` | 本枚火箭幂等 ID |
| `level` | 点火等级 |
| `fuel_threshold` | 点火阈值 |
| `ignited_at_ms` | 点火时间 |
| `launch_at_ms` | 预计发射时间 |
| `reset_at_ms` | UTC 日重置时间 |
| `top1_user_id` | 点火时贡献第一 |
| `igniter_user_id` | 点火人 |
| `config_version` | 点火配置版本 |
| `cover_url` | 小封面图 |

## 送礼加燃料

1. `SendGift` 先调用 wallet 扣费。
2. 扣费成功后，Room Cell 串行更新热度、礼物榜和火箭。
3. 燃料来自 wallet 返回的 `heat_value`。
4. 当前等级未满时，增加 `min(本次燃料, 剩余阈值)`。
5. 超过阈值的燃料直接写入 `overflow_fuel`，不进入下一等级。
6. 当前等级满后：
   - 写入 `RoomRocketFuelChanged`。
   - 写入 `RoomRocketIgnited`。
   - 将当前火箭追加到 `pending_launches`。
   - 如果未到 5 级，立刻把当前状态切到下一等级，`current_fuel=0`。
   - 如果 5 级已满，状态切到 `completed`，UTC 重置前不再累计新火箭。

## 发射结算

1. `RoomRocketIgnited` 被 outbox worker 处理后，安排 RocketMQ delayed wakeup。
2. 本地扫描 worker 也会扫描已加载且仍持有 lease 的 Room Cell，作为兜底。
3. 到点后 `LaunchRoomRocket` 命令重新加载 Room Cell。
4. 命令必须校验：
   - `rocket_id` 存在于 `pending_launches`。
   - `level` 匹配。
   - `launch_at_ms <= now_ms`。
   - `now_ms < reset_at_ms`。
5. 结算时按发射瞬间 `OnlineUsers` 抽在房奖励。
6. `top1` 和 `igniter` 使用点火时锁定的用户，离房后仍发对应奖励。
7. 发奖完成后移除该 `pending_launches` 项，当前正在累计的火箭不被发射命令推进。

## 奖励

| 奖励 | 用户来源 |
| --- | --- |
| 点火人奖励 | `pending_launches[].igniter_user_id` |
| 贡献第一奖励 | `pending_launches[].top1_user_id` |
| 在房奖励 | 发射命令执行时 Room Cell `OnlineUsers` |

发放规则：

| 奖励 | 规则 |
| --- | --- |
| 在房奖励 | 每个在线用户从 `inRoomRewards` 按权重随机抽 1 个 |
| 贡献第一奖励 | top1 用户获得 `top1Rewards` 配置池内全部奖励 |
| 点火人奖励 | 点火用户获得 `igniterRewards` 配置池内全部奖励 |

同一用户同时是 top1 和点火人时，top1 池和点火人池继续叠加；如果该用户发射瞬间仍在房间，也会再拿 1 个在房随机奖励。

## IM

| 事实 | IM | 投递方 |
| --- | --- | --- |
| `RoomRocketFuelChanged` | `room_rocket_fuel_changed` | room-service IM bridge |
| `RoomRocketIgnited` | `room_rocket_ignited` | room-service IM bridge |
| `RoomRocketIgnited` | `room_rocket` 区域/全局飘屏 | activity-service |
| `RoomRocketLaunched` | `room_rocket_launched` | room-service IM bridge |
| `RoomRocketRewardGranted` | `room_rocket_reward_granted` 房间奖励 IM | activity-service |

奖励 IM 由 activity-service 查询用户公开资料后组装，包含头像、昵称、短 ID 和奖励信息。room-service 不反查 user-service。
奖励资源组内容不写入 Room Cell；客户端需要金币数量、有效期、聊天气泡或麦位特效素材时，通过 rocket GET 接口读取展开后的 `resource_group`。

## 高频处理

- Room Cell 串行处理送礼命令，所以同房高频送礼不会并发写乱进度。
- 满阈值时只把火箭追加到 `pending_launches`，不会阻塞下一等级继续累计。
- 房间状态 IM 通过 room outbox 补偿投递，不在送礼主链路同步调用腾讯 IM。
- 奖励 IM 合并为一条消息，`rewards` 内包含所有中奖人，避免按用户逐条堆积。
- RocketMQ delayed wakeup 只负责唤醒，最终发射仍由 Room Cell 命令校验。

## UTC 重置

- 火箭周期按 UTC 自然日。
- 到达 `reset_at_ms` 后，查询或下一次送礼会投影为 1 级新进度。
- 跨过 UTC 日界仍未发射的待发射火箭不结算。

## 后台配置

配置表：`room_rocket_configs`

| 字段 | 说明 |
| --- | --- |
| `enabled` | 是否开启 |
| `fuel_source` | 固定 `heat_value` |
| `launch_delay_ms` | 点火到发射延迟 |
| `broadcast_enabled` | 是否发飘屏 |
| `broadcast_scope` | `region`、`global`、`none` |
| `reward_stack_policy` | 历史兼容字段；当前特殊身份奖励按上方规则叠加 |
| `levels_json` | 5 级阈值、物料和奖励池 |
| `gift_fuel_rules_json` | 礼物燃料规则 |

`levels_json` 字段使用 `fuelThreshold`、`coverUrl`、`animationUrl`、`launchAnimationUrl`、`launchedImageUrl`、`inRoomRewards`、`top1Rewards`、`igniterRewards`。
