# Lucky Gift Product Design

本文档定义语音房幸运礼物的产品规则。幸运礼物使用金币赠送，奖励也返还金币

## Positioning

| Object | Meaning                                        |
| ------ | ---------------------------------------------- |
| 用户   | 用金币赠送幸运礼物，获得房间表现和概率金币奖励 |
| 主播   | 正常获得礼物贡献和房间热度，不参与控制开奖结果 |
| 房间   | 获得飘屏、热度、本地榜、互动气氛               |
| 平台   | 用奖池和风控控制整体返还率，不控制单个用户输赢 |

核心原则：

| Rule         | Decision                                           |
| ------------ | -------------------------------------------------- |
| 先扣费再抽奖 | 只有金币扣费成功后才进入幸运礼物抽奖               |
| 先过滤再随机 | 先根据奖池水位和风控阈值过滤奖档，再执行随机       |
| 抽后不改结果 | 随机结果生成后必须落日志，不允许后台强改           |
| 整体控阈值   | 控制平台、房间、礼物、用户维度的风险敞口           |
| 规则可解释   | 前台展示基础概率、奖励范围、低水位规则和上限       |
| 结果可审计   | 每次抽奖保存规则版本、奖池快照、随机材料和发奖状态 |

## User Experience

用户在礼物面板选择幸运礼物，支付金币后出现三类结果：

| Result       | User Feedback                                      |
| ------------ | -------------------------------------------------- |
| 未命中奖励   | 礼物正常送出，房间展示普通幸运礼物消息             |
| 命中小额奖励 | 礼物正常送出，返还金币到账，房间展示轻量提示       |
| 命中大奖励   | 礼物正常送出，返还金币到账，房间展示飘屏或强化特效 |

金币奖励必须以实际到账为准。若账务补偿中，客户端展示“奖励处理中”，不能提前展示“已到账”。

## Three Pool Model

三层奖池仍然是资金水位和风险敞口模型，不能被新手、中级、高级体验池替代。体验池只决定同一次抽奖可见的奖档分布，实际能不能发奖仍然由平台、房间、礼物三层水位以及全站/礼物基础 RTP 窗口共同决定。

### Platform Pool

平台总奖池控制全站幸运礼物的整体返还率。

| Field                         | Meaning                  |
| ----------------------------- | ------------------------ |
| `platform_pool_balance`       | 当前全站可用幸运奖励金币 |
| `platform_reserve_floor`      | 平台最低安全水位         |
| `platform_target_payout_rate` | 平台目标返还率           |
| `platform_daily_payout_limit` | 平台每日最大奖励支出     |

平台奖池低于安全水位时，大额奖档不可参与随机；被过滤奖档的概率质量进入未中奖奖档，不重新分配给其他奖档。

### Room Pool

房间奖池控制单房间爆奖能力。热门房可以积累更高水位，冷房不能打穿平台预算。

| Field                      | Meaning                  |
| -------------------------- | ------------------------ |
| `room_pool_balance`        | 房间当前可用幸运奖励金币 |
| `room_reserve_floor`       | 房间最低安全水位         |
| `room_hourly_payout_limit` | 房间小时最大奖励支出     |
| `room_daily_payout_limit`  | 房间每日最大奖励支出     |

房间奖池只影响本房间，不允许直接挪用其他房间水位。需要运营活动加成时，必须通过平台奖池或活动预算显式补贴。

### Gift Pool

礼物奖池控制不同幸运礼物的成本结构。

| Field                     | Meaning                      |
| ------------------------- | ---------------------------- |
| `gift_pool_balance`       | 某个幸运礼物当前可用奖励金币 |
| `gift_reserve_floor`      | 礼物最低安全水位             |
| `gift_target_payout_rate` | 礼物目标返还率               |
| `gift_max_single_payout`  | 礼物单次最高奖励             |

低价礼物适合高频小奖，高价礼物适合低频大奖。不同礼物必须使用独立概率表和独立水位，不要共用一个不可解释的全局概率。

## Five Layer Control Model

幸运礼物 V2 使用五层控制模型。三层资金水位继续负责“能不能付”，五层控制负责“钱怎么算、体验怎么呈现、补贴归谁”。

| Layer | Product Role | Cost Accounting | Hard Boundary |
| --- | --- | --- | --- |
| 全站 RTP 控制窗口 | 保证全站基础返奖成本 | 计入 `base_reward_coins`，进入全站基础 RTP | 不能被房间气氛、活动补贴污染 |
| 礼物 RTP 子窗口 | 控制单个幸运礼物成本结构 | 同一笔 `base_reward_coins` 同时进入礼物基础 RTP | 单礼物不能长期借其他礼物的 RTP |
| 用户体验池 | 新手、中级、高级奖档和节奏 | 不单独记成本，只选择基础候选奖档 | 不承诺单用户 RTP |
| 房间气氛池 | 控制房间爆点、共享奖励、飘屏强度 | 计入 `room_atmosphere_reward_coins` 或表现预算 | 不回写 Room Cell 核心状态 |
| 活动预算池 | 节日、榜单、运营补贴 | 计入 `activity_subsidy_coins` | 独立预算，不能冲抵基础 RTP |

用户看到的总返奖拆成三类：

```text
effective_reward_coins = base_reward_coins
                       + room_atmosphere_reward_coins
                       + activity_subsidy_coins
```

成本口径必须分开：

| Metric | Formula | Meaning |
| --- | --- | --- |
| `base_rtp` | `sum(base_reward_coins) / sum(coin_spent)` | 平台承诺和风控使用的基础 RTP |
| `gift_base_rtp` | `sum(base_reward_coins by gift_id) / sum(coin_spent by gift_id)` | 单礼物成本健康度 |
| `effective_rtp` | `sum(effective_reward_coins) / sum(coin_spent)` | 用户实际感受到的总返还 |
| `subsidy_cost` | `sum(room_atmosphere_reward_coins + activity_subsidy_coins)` | 运营和房间气氛额外成本 |

运营要提高娱乐性时，优先调整体验池、房间气氛池和活动预算池；不要直接把基础 RTP 拉高。基础 RTP 是平台成本底线，不是用户体感的唯一来源。

## Experience Pools

新手池、中级池、高级池是同一个幸运礼物规则下的三个体验配置。它们不单独拥有金币余额，不直接绕过平台、房间、礼物三层奖池，不承诺单个用户固定 RTP。

| Pool | Entry Rule | Product Shape | Payout Boundary |
| --- | --- | --- | --- |
| `novice` | 用户当前幸运礼物累计付费抽数较低，或处于新手保护期 | 高频小奖、少量返还、禁止超高倍大奖 | 单次奖励上限最低，避免新用户误解为高波动博彩 |
| `intermediate` | 用户已经完成基础体验但未进入高频阶段 | 小奖和中等奖混合，中奖频率下降 | 允许中等奖，不允许消耗高级奖池预算 |
| `advanced` | 高频幸运礼物用户或高价幸运礼物 | 低频高倍、强调飘屏和房间表现 | 单次奖励上限最高，但必须被总 RTP、三层水位和风控同时约束 |

默认分池建议按付费抽数切换，服务端只使用 UTC 计数，不使用客户端本地时间：

| Pool | Default Draw Range | Notes |
| --- | ---: | --- |
| `novice` | `[1, 2000]` | 首次体验阶段，奖档更密但倍数更低 |
| `intermediate` | `[2001, 20000]` | 常规用户阶段，保留中等奖表现 |
| `advanced` | `[20001, +∞)` | 高频阶段，大奖概率更低但上限更高 |

分池只影响基础候选奖档和表现节奏。总返还率必须由全站 RTP 控制窗口和礼物 RTP 子窗口共同收敛，不能依赖三个体验池各自“碰运气”。

### Stage Feedback

阶段反馈是体验反馈，不是单用户保底。默认在新手池内按付费抽数进度触发：`1`、`25%`、`50%`、`75%`、`100%` of `novice_draw_limit`。

| Feedback Type | Cost Accounting | Allowed Behavior |
| --- | --- | --- |
| 进度反馈 | `presentation_only` | 展示阶段、进度、动画、房间幸运值，不发金币 |
| 小额反馈奖 | `base_rtp` or `activity_subsidy` | 如果发金币，必须抽前进入基础候选或活动预算候选 |
| 阶段完成提示 | `presentation_only` | 告知用户已离开新手池或进入常规池 |

阶段反馈不能绕过 RTP、三层水位和风控上限。任何“第 N 抽必给金币”的设计都必须先占用明确预算，否则会把基础 RTP 控制窗口打穿。

## RTP Control Model

单纯固定概率表不能保证 `10w` 次上下浮动不超过 `1%`、`100w` 次上下浮动不超过 `0.5%`，尤其是高级池包含高倍奖时。幸运礼物必须使用“概率体验 + 整数 RTP 控制窗口”的组合模型。

控制规则：

| Rule | Decision |
| --- | --- |
| 控制口径 | 每次抽奖同时进入全站 RTP 窗口和礼物 RTP 子窗口 |
| 控制窗口 | 默认 `global_window_draws=100000`，礼物子窗口可按流量配置更大窗口 |
| RTP 输入 | 使用 `target_rtp_ppm` 保存，例如 `95% = 950000` |
| 目标支出 | `window_target_payout = floor((window_wager * target_rtp_ppm + carry_ppm) / 1000000)` |
| 余数处理 | 除不尽的 ppm 余数进入下一个窗口的 `carry_ppm` |
| 候选过滤 | 每次抽奖前过滤三层水位、风控、体验池奖档和窗口剩余可支付额 |
| 随机范围 | 只在过滤后的候选奖档中随机，随机后不允许后台改结果 |
| 窗口补偿 | 全站或礼物窗口剩余抽数不足以自然追平目标时，抽前启用基础 RTP 补偿候选奖档 |

窗口内每次抽奖都维护两个整数：

```text
global_remaining_draws = global_window_draws - global_window_paid_draws
global_remaining_payout = global_window_target_payout - global_window_base_payout

gift_remaining_draws = gift_window_draws - gift_window_paid_draws
gift_remaining_payout = gift_window_target_payout - gift_window_base_payout
```

候选奖档必须满足：

```text
base_reward_coins <= global_remaining_payout
base_reward_coins <= gift_remaining_payout

global_remaining_payout - base_reward_coins
  <= global_min_future_max_reward * (global_remaining_draws - 1)

gift_remaining_payout - base_reward_coins
  <= gift_min_future_max_reward * (gift_remaining_draws - 1)
```

其中 `*_min_future_max_reward` 是对应窗口后续任意体验池至少能支付的单次最大基础返奖。这个约束保证窗口不会因为前面连续未中奖而在最后几抽无法追平基础 RTP。

达成条件：

| Sample Size | Required Result | Control Requirement |
| ---: | --- | --- |
| `100000` paid draws | 全站基础 RTP 与目标 RTP 绝对偏差 `< 1%` | 一个完整全站控制窗口内目标支出按整数配额结清 |
| `1000000` paid draws | 全站基础 RTP 与目标 RTP 绝对偏差 `< 0.5%` | 十个完整全站控制窗口累计偏差只剩整数余数 |
| 单礼物流量达到配置窗口 | 礼物基础 RTP 收敛到礼物目标 | 礼物子窗口独立结清，不能长期靠全站窗口兜底 |

这个保证只适用于控制口径内的总 paid draws，不适用于单个用户、单个房间或单个很小流量分桶。单个用户仍然可能亏损或盈利；平台只能承诺全站和礼物基础 RTP 收敛。

如果三层奖池水位、风控硬上限或钱包入账失败导致基础返奖无法支付，系统必须记录 `rtp_underpay` 风险事件并降低基础 RTP，不能透支奖池或抽后篡改结果。房间气氛和活动补贴也不能用于“补平”基础 RTP，只能提高用户看到的 `effective_rtp`。

## Risk Layer

风控层不在抽奖后篡改结果，只在抽奖前决定是否允许参与、是否降低可用奖档、是否进入人工观察。

| Threshold                         | Product Meaning          |
| --------------------------------- | ------------------------ |
| `max_single_payout`               | 单次最大奖励金币         |
| `max_user_daily_payout`           | 单用户每日最大奖励金币   |
| `max_device_daily_payout`         | 单设备每日最大奖励金币   |
| `max_room_hourly_payout`          | 单房间每小时最大奖励金币 |
| `max_anchor_daily_related_payout` | 单主播关联奖励风险上限   |
| `max_activity_period_payout`      | 活动周期最大奖励金币     |
| `max_user_hit_count_window`       | 单用户窗口期最大命中次数 |

上限制定依据：

```text
expected_payout(window) = paid_draw_capacity(window)
                        * gift_price
                        * target_rtp_ppm
                        / 1000000

single_cap <= min(gift_sub_window_target_payout, single_event_risk_budget)
hourly_cap >= expected_payout(user_hour_window) * volatility_factor
daily_cap >= expected_payout(user_day_window) * volatility_factor
```

默认建议：

| Limit | Base Formula | Why |
| --- | --- | --- |
| 单次上限 | `gift_price * max_allowed_multiplier` | 控制单事件最大曝光，直接决定是否允许 100x/500x |
| 小时上限 | `expected_hourly_payout * 1.5~3.0` | 覆盖正常波动，同时限制短时异常套利 |
| 日上限 | `expected_daily_payout * 1.2~2.0` | 覆盖高活跃用户正常体验，同时保护单日预算 |
| 高水位开高倍 | `min(global_headroom, gift_headroom, pool_headroom) >= high_reward` | 只有此前少付或奖池积累足够时，才允许高倍进入候选 |

如果小时或日上限低于对应窗口的期望基础返奖，系统将无法同时满足用户上限和全站 RTP。此时必须降低目标 RTP、降低用户可抽容量、提高上限，或把额外体验放到活动预算池，而不是让基础 RTP 窗口透支。

高水位定义：

```text
rtp_headroom = remaining_base_payout
             - remaining_draws * gift_price * target_rtp_ppm / 1000000

high_multiplier_enabled = global_rtp_headroom >= tier_reward
                       && gift_rtp_headroom >= tier_reward
                       && platform_pool_headroom >= tier_reward
                       && room_pool_headroom >= tier_reward
                       && gift_pool_headroom >= tier_reward
```

基础最小版本暂时只验证 `global_rtp_headroom` 和 `gift_rtp_headroom`。接入真实奖池后，必须同时校验平台、房间、礼物三层水位。

线上标准强制口径：

| Control | Required Behavior |
| --- | --- |
| 全站 RTP 窗口 | 每一笔付费抽奖都进入，`base_reward_coins` 必须按窗口整数配额结清 |
| 礼物 RTP 子窗口 | 同一笔基础返奖同时进入礼物窗口，单礼物不能借其他礼物的成本 |
| 平台/房间/礼物三层池 | 每抽先按 `pool_rate_ppm` 入池，再按责任权重支付基础返奖；任一层低于 reserve floor 时过滤对应奖档 |
| 新手/中级/高级体验池 | 只控制候选奖档和节奏，不持有预算，不承诺单用户 RTP |
| 单次/小时/日/设备/房间/主播上限 | 抽前过滤所有会超过剩余额度的付费候选，抽后只更新计数 |
| 高水位高倍 | 只有全站窗口、礼物窗口、三层池水位同时满足高倍奖励时，100x/500x 才能进候选 |
| 房间气氛池 | 独立预算，只提升房间爆点和共享奖励，不写入基础 RTP |
| 活动预算池 | 独立补贴，只提高 `effective_rtp`，不能冲抵基础 RTP 欠付 |
| 钱包和房间表现 | 扣费必须先成功；返奖和房间表现通过 outbox 重试，不能在持有奖池锁时调用外部 RPC |

触发阈值后的产品动作：

| Action     | When                                       |
| ---------- | ------------------------------------------ |
| 拒绝参与   | 用户、设备、房间或活动已达到硬上限         |
| 低水位模式 | 奖池不足以支付完整奖档                     |
| 仅小奖模式 | 大奖风险超出当前窗口预算                   |
| 人工观察   | 主播、用户、设备、支付来源存在明显关联异常 |
| 活动暂停   | 平台总返还率或投诉风险超过运营阈值         |

## Reward Configuration

每个幸运礼物最小配置：

| Field                       | Meaning                    |
| --------------------------- | -------------------------- |
| `gift_id`                   | 幸运礼物 ID                |
| `gift_price`                | 单个礼物金币价格           |
| `pool_rate`                 | 每笔礼物进入幸运奖池的比例 |
| `target_payout_rate`        | 长期目标返还率             |
| `target_rtp_ppm`            | RTP 控制窗口使用的目标返还率 |
| `control_window_draws`      | RTP 控制窗口 paid draws 数 |
| `gift_rtp_window_draws`     | 单礼物 RTP 子窗口 paid draws 数 |
| `experience_pool_rules`     | 新手、中级、高级分池进入规则 |
| `room_atmosphere_rule`      | 房间爆点、共享奖励和表现规则 |
| `activity_budget_id`        | 当前可用活动补贴预算 ID |
| `reserve_floor`             | 最低安全水位               |
| `max_single_payout`         | 单次最高奖励金币           |
| `max_user_daily_payout`     | 单用户每日最高奖励金币     |
| `max_room_hourly_payout`    | 单房间小时最高奖励金币     |
| `probability_table_version` | 概率表版本                 |
| `enabled_reward_tiers`      | 当前启用奖档               |

示例权重表只表达结构，不作为正式运营参数。体验池权重用于表现节奏，不直接决定总 RTP。

| Pool | Tier | Reward | Base Weight | Availability |
| --- | --- | ---: | ---: | --- |
| `novice` | `none` | `0` | `270000` | always |
| `novice` | `rebate_0_5x` | `gift_price * 0.5` | `200000` | pool safe |
| `novice` | `rebate_1x` | `gift_price * 1` | `350000` | pool safe |
| `novice` | `small_2x` | `gift_price * 2` | `140000` | pool safe |
| `novice` | `small_5x` | `gift_price * 5` | `35000` | pool safe |
| `novice` | `small_10x` | `gift_price * 10` | `5000` | pool safe |
| `intermediate` | `none` | `0` | `760000` | always |
| `intermediate` | `rebate_1x` | `gift_price * 1` | `100000` | pool safe |
| `intermediate` | `small_2x` | `gift_price * 2` | `80000` | pool safe |
| `intermediate` | `medium_5x` | `gift_price * 5` | `45000` | pool safe and user limit safe |
| `intermediate` | `medium_20x` | `gift_price * 20` | `13000` | high water only |
| `intermediate` | `medium_50x` | `gift_price * 50` | `2000` | high water only |
| `advanced` | `none` | `0` | `900000` | always |
| `advanced` | `small_2x` | `gift_price * 2` | `50000` | pool safe |
| `advanced` | `medium_5x` | `gift_price * 5` | `30000` | pool safe |
| `advanced` | `large_20x` | `gift_price * 20` | `15000` | high water only |
| `advanced` | `large_100x` | `gift_price * 100` | `4500` | high water only |
| `advanced` | `jackpot_500x` | `gift_price * 500` | `500` | high water and risk safe |

奖池低水位时，如果 `large_100x` 不可用，它的权重必须进入 `none`，不能转给其他奖档。RTP 控制窗口需要追平目标时，只能在抽前选择当前可支付的补偿奖档，不能在随机后把未中奖改成中奖。

## V2 Draw Algorithm

V2 抽奖算法把“基础返奖”和“娱乐补贴”分开算。基础返奖负责 RTP，房间气氛和活动补贴负责体验。

```text
input:
  command_id, user_id, room_id, gift_id, gift_price, paid_at_ms

state:
  global_rtp_window
  gift_rtp_window(gift_id)
  platform_pool, room_pool(room_id), gift_pool(gift_id)
  user_experience_state(user_id, gift_id)
  room_atmosphere_pool(room_id)
  activity_budget(optional)
  risk_counters
```

标准算法：

```text
1. 用 command_id 做幂等查询，已有结果直接返回
2. 选择用户体验池：novice / intermediate / advanced
3. 计算阶段反馈点；反馈只产生展示事件或已预算候选，不直接发未预算金币
4. 锁定全站 RTP 窗口和礼物 RTP 子窗口
5. 锁定平台、房间、礼物三层资金水位
6. 锁定房间气氛池和可用活动预算
7. 读取风险计数和用户窗口状态
8. 计算基础返奖容量：
   global_base_capacity
   gift_base_capacity
   platform_capacity
   room_capacity
   gift_capacity
   risk_capacity
9. 计算单次、小时、日、设备、房间、主播和活动周期剩余额度
10. 计算高倍奖档水位；只有全站、礼物和三层奖池高水位都足够时才开放高倍
11. 从体验池生成基础候选奖档
12. 过滤所有无法同时满足全站 RTP、礼物 RTP、三层水位和风控的基础奖档
13. 生成房间气氛候选：飘屏、共享红包、房间幸运值、表现强化
14. 生成活动补贴候选：限时加奖、榜单补贴、节日额外返奖
15. 合并候选并执行一次随机
16. 写入抽奖事实、窗口快照、预算快照和随机审计材料
17. 更新全站 RTP 窗口和礼物 RTP 子窗口，仅写入 base_reward_coins
18. 更新房间气氛池和活动预算，仅写入各自补贴支出
19. 提交事务
20. 事务后通过 wallet-service 发金币，通过 room-service/腾讯云 IM 做房间表现
```

候选奖档必须带预算来源：

| Budget Source | Pays Coins | Counts In Base RTP | Typical Use |
| --- | --- | --- | --- |
| `base_rtp` | yes | yes | 常规小奖、中奖、大奖 |
| `room_atmosphere` | optional | no | 房间红包、共享奖励、飘屏强化 |
| `activity_subsidy` | yes | no | 节日加奖、榜单补贴、限时返利 |
| `presentation_only` | no | no | 动画、称号、房间幸运值 |

随机前必须完成过滤。随机后只能落账、补偿或返回 pending，不能把结果改成另一个奖档。

## Draw Flow

标准流程：

```text
读取规则版本
按用户累计付费抽数选择 novice/intermediate/advanced 体验池
读取平台、房间、礼物奖池水位
读取或创建全站 RTP 窗口和礼物 RTP 子窗口
读取房间气氛池和活动预算
读取用户、设备、房间、主播、活动风控计数
过滤不可支付、不可触发或会破坏基础 RTP 窗口追平能力的基础奖档
生成基础返奖、房间气氛、活动补贴候选奖档
执行随机
写入抽奖结果日志
发放金币奖励
广播房间表现
```

不可接受流程：

```text
先随机
发现超预算
后台改成未中奖
```

## Room Presentation

幸运礼物产生两类房间表现：

| Event          | Owner                                                |
| -------------- | ---------------------------------------------------- |
| 礼物送出消息   | `room-service`                                       |
| 房间热度变化   | `room-service`                                       |
| 房间本地礼物榜 | `room-service`                                       |
| 幸运奖励结果   | `activity-service` 决策，`room-service` 转成房间表现 |
| 房间消息投递   | 腾讯云 IM                                            |

`activity-service` 不直接持有 Room Cell，不修改麦位、禁言、在线态和房间榜。它只返回幸运礼物决策结果，并通过事件让房间系统表现出来。

## Compliance Baseline

产品默认遵守以下红线：

| Red Line             | Decision                             |
| -------------------- | ------------------------------------ |
| 不允许空奖池承诺大奖 | 当前奖池无法支付时，大奖档必须不可用 |
| 不删除抽奖记录       | 抽奖、奖池、发奖记录按审计周期保存   |

## Metrics

上线后必须监控这些指标：

| Metric                  | Reason               |
| ----------------------- | -------------------- |
| `lucky_gift_gmv`        | 幸运礼物金币消耗     |
| `lucky_base_reward_payout` | 基础 RTP 金币奖励支出 |
| `room_atmosphere_payout` | 房间气氛和共享奖励支出 |
| `activity_subsidy_payout` | 活动补贴支出 |
| `base_rtp` | 基础返还率，平台成本口径 |
| `effective_rtp` | 用户可见返还率，含补贴 |
| `gift_base_rtp` | 单礼物基础返还率 |
| `pool_balance_by_scope` | 平台、房间、礼物水位 |
| `room_atmosphere_balance` | 房间气氛预算水位 |
| `activity_budget_balance` | 活动补贴预算水位 |
| `hit_rate_by_tier`      | 各奖档命中率         |
| `reward_credit_latency` | 金币到账延迟         |
| `risk_reject_count`     | 风控拒绝次数         |
| `pending_reward_count`  | 待补偿发奖数量       |
| `complaint_rate`        | 用户争议风险         |

## V1 Scope

首版只做：

| Included           | Excluded               |
| ------------------ | ---------------------- |
| 三层奖池           | 主备热同步             |
| 固定概率表版本     | 个性化概率             |
| 抽前风控阈值       | 抽后改结果             |
| 金币奖励           | 现金、实物、提现       |
| 房间飘屏和系统消息 | 跨房 PK 大奖池         |
| 抽奖日志和补偿     | 后台人工指定某用户中奖 |
