# 部署:多用户雾鸦岛

把游戏部署成「每个用户看自己的世界、邀请码门禁、离开暂停续播」的服务。

## 架构

```
浏览器 ──cookie(gsid)──► live_server.py ──► EngineManager(引擎池)
                              │                   ├─ 会话A:独立 WorldEngine + 线程(在烧 token)
                              │                   └─ 会话B:独立 WorldEngine + 线程
                              └──► SQLite(sessions.db)
                                     ├─ invites:邀请码(一码一会话)
                                     └─ sessions:每用户进度(引擎快照 JSON)
```

- **一码一会话**:邀请码首次激活即绑定一个会话并种 cookie(`gsid`),该码作废;之后靠 cookie 续播,不再要码。
- **离开即暂停续播**:页面有心跳(每 25s)→ 引擎在跑;闲置超时(默认 90s 无心跳)→ 存快照到 SQLite + 停引擎 + 移出内存,**不再烧 token**;用户回来从快照恢复、接着原进度演。
- **并发上限**:活跃引擎数封顶(默认 8);超了把最久没心跳的会话挤下去(存档暂停)。
- **成本隔离**:每会话一个 MeteredLLM,底层 LiteLLM 客户端无状态、多会话共享。
- **鉴权边界**:静态资源(HTML/JS/音频)公开、不耗 token;凡触发引擎的 `/api/*` 都要有效会话 cookie,没有就回 `401 {need_invite:true}`,前端弹邀请码页。

## 启动

```bash
# 1) 生成邀请码(打印到屏幕,同时写入 SQLite)
GENESIS_WORLD=ravenisle ~/miniconda3/envs/genesis-world/bin/python -m genesis.server.live_server gen 20

# 2) 启动服务
GENESIS_WORLD=ravenisle ~/miniconda3/envs/genesis-world/bin/python -m genesis.server.live_server
# → http://<host>:8000/src/genesis/viz/web/mystery.html
```

## 环境变量

| 变量 | 默认 | 说明 |
| --- | --- | --- |
| `GENESIS_WORLD` | `ravenisle` | 世界目录 |
| `GENESIS_HOST` | `0.0.0.0` | 监听地址(部署用 0.0.0.0) |
| `GENESIS_PORT` | `8000` | 端口 |
| `GENESIS_DB` | `worlds/<world>/saves/sessions.db` | SQLite 路径 |
| `GENESIS_MAX_ENGINES` | `8` | 同时活跃世界上限(控总 token) |
| `GENESIS_IDLE_TIMEOUT` | `90` | 无心跳多少秒后暂停存档 |
| `GENESIS_BRAIN` | `llm` | `llm`(MiniMax-M3)/`stub`(零成本验证) |
| `GENESIS_RATE` | `30` | 时钟倍率 |
| `LLM_PROVIDER` | `minimax` | `.env` 里配;`minimax`/`deepseek` |

密钥(`MINIMAX_API_KEY`/`ELEVENLABS_API_KEY` 等)放 `.env`,不入代码。

## token 成本上界

最坏情况 ≈ `GENESIS_MAX_ENGINES` 个世界同时跑。要更省就调小并发上限、缩短 `GENESIS_IDLE_TIMEOUT`。单世界开发调试仍用 `genesis.server.live2`(单进程、无门禁)。

## 反向代理(可选)

生产建议放在 nginx/caddy 后做 TLS;cookie 已设 `HttpOnly; SameSite=Lax`,HTTPS 下可再加 `Secure`(改 `live_server.py` 的 Set-Cookie)。
