## MODIFIED Requirements

### Requirement: 房间作用域与创建
系统 SHALL 按会话作用域管理赛马房间。群聊场景 MUST 以 `group_id` 作为房间键；测试模式私聊场景 MUST 以 `test_<user_id>` 作为房间键。单一作用域同一时间 SHALL 最多存在一个活动房间。房间在首位用户发送报名命令且该作用域不存在活动房间时自动创建。`finished` 或 `interrupted` 房间在结果公告后自动释放，允许同作用域立即创建新房间。正式模式下群聊 MUST 仅对 `ALLOWED_GROUPS` 配置列表中的群开放。

#### Scenario: 群聊首次报名自动建房
- **WHEN** 群 `10001` 中第一位用户发送报名命令且该群不存在活动房间
- **THEN** 系统创建属于 `group_id=10001` 的新房间，并将该用户登记为首位参赛者

#### Scenario: 私聊测试使用独立房间键
- **WHEN** 测试人员在私聊中发送报名命令且 `TEST_MODE=True`
- **THEN** 系统创建房间键为 `test_<user_id>` 的测试房间，不影响任何群聊房间

#### Scenario: 正式模式下非白名单群拒绝
- **WHEN** `TEST_MODE=False` 且用户在不在 `ALLOWED_GROUPS` 列表中的群发送赛马命令
- **THEN** 系统拒绝请求，并提示该群未开放赛马功能

#### Scenario: 比赛结束后立即可建新房
- **WHEN** 某群的房间状态为 `finished` 且结果已公告
- **THEN** 系统释放该房间，用户再次发送报名命令可创建新房间

### Requirement: 房间状态恢复
系统 SHALL 在关键状态变化后持久化活动房间快照，并在插件重启后重建可恢复房间。关键持久化点包括：房间创建、报名/取消报名、下注/退还、比赛开始、比赛结束、房间取消。若重启后发现 `running` 状态的房间，系统 MUST 将其标记为 `interrupted`，自动退还所有有效下注，并向用户发送中断通知。持久化数据 MUST 包含：房间元数据（scope、状态、创建时间）、马匹列表（owner、名称、位置、状态）、下注记录（user_id、马匹名、金额）、当前赔率快照。数据库操作 MUST 使用 `aiosqlite` 进行异步 IO，避免阻塞 NoneBot 事件循环。

#### Scenario: 重启后恢复等待中的房间
- **WHEN** 插件重启前某群房间状态为 `waiting` 且快照已落盘
- **THEN** 插件启动时重新加载该房间，使用户可以继续报名、下注或开赛

#### Scenario: 重启后处理中断中的比赛
- **WHEN** 插件重启前某房间状态为 `running` 但推进任务已丢失
- **THEN** 插件启动时将房间标记为 `interrupted`，退还所有有效下注，并通知用户该场比赛已因中断取消

#### Scenario: 数据库操作异步化
- **WHEN** 系统执行任何 SQLite 持久化操作
- **THEN** 系统通过 `aiosqlite` 异步连接执行，不阻塞事件循环

### Requirement: 比赛结果持久化
系统 SHALL 在每场比赛结束后将结果持久化到 `data/group_horse_racing/race.db` 的历史记录表中。记录 MUST 包含：比赛 ID、作用域、冠军马匹名称和主人、所有参赛者列表、下注分布概要（各马下注总额和人次）、比赛时长（tick 数）、完成时间戳、积分变化详情（每位用户的赔付/奖励/退还金额及原因摘要）。历史数据不设置自动清理策略。

#### Scenario: 比赛结束后保存历史
- **WHEN** 一场比赛完成结算
- **THEN** 系统将比赛结果写入历史记录表，包含冠军信息、参赛者、下注分布、比赛时长和积分变化详情

#### Scenario: 查询历史时显示积分变化
- **WHEN** 用户查询某场比赛的历史记录
- **THEN** 系统返回包含每位用户的赔付/奖励/退还金额及原因的积分变化摘要
