# Noosphere（意识海）· 模型卡与部署手册

> 版本：apw3（2026-10-23 上线）　权重 SHA256 `b965762e89e4f4209ed2c017f38fa28e73e14a466d549725ef2845382a315b8d`
> 一句话：**46.8 万参数的纯 JavaScript Transformer，接管「战双PRO」人机 AI 的「出哪张牌 / 打谁」两个决策点。**

---

## 1. 它是什么，替谁做决定

引擎里 AI 原本靠三条手写启发式（`evaluateCardValue` / `evaluateAttackValue`）打分。Noosphere 顶掉的正是这两个打分函数：

| 决策 | 原来谁做 | 现在 | 输出 |
|---|---|---|---|
| **出哪张牌** | `evaluateCardValue`（DP×100 + PP/100 − 费用×50 + 关键词加成） | 模型 policy 头 | 12 个手牌位的分数 |
| **打谁** | `evaluateAttackValue`（PP 比较，且有「有角色必打角色」硬规则） | 模型 att 头 | 5 个角色目标 + 「打玩家」共 6 个分数 |
| 阻挡 | 引擎「安全击破才挡」启发式 | 模型 blk 头（`?noo=1` 时经 `__zsNooBlockScore` 接管，2026-10-23 P1） | 30 类 logit |
| 进化 / 效果动作 / 选择链答案 / 局面估值 | 引擎自己的规则 | 模型也能算（evo/act/sel/value），**当前未接入** | — |

接入方式是 feature flag（`window.__zsNooCardScore` / `__zsNooAttackScore` 两个钩子）：
钩子不存在 = 走原启发式，行为一字不变；钩子返回 `NaN` = 该局面交还引擎。**不改规则引擎，只换打分。**

## 2. 它不做什么（重要边界）

- **不看卡面效果文本**。466 张卡的效果是代码（`js/10-card-effects-main.js` 447KB + `js/34-trigger-bus.js` 626KB），模型只看「卡号 + 费用/PP/DP/类型/是否横置」和局面布局。效果交互、连锁、时机判断仍在引擎里。
- **不管规则判定、结算、回合调度**。它只回答「这张牌/这个目标打多少分」。
- **不是通用 TCG 模型**。词表、槽位布局、特征口径都绑定本作，换游戏要重做数据管线。

## 3. 结构

```
47 个 token 槽位
 0        全局 token
 1–5      我方场面（角色）
 6–17     我方手牌（最多 12）
 18–29    我方费区
 30–34    对方场面
 35–46    对方费区
```
- 每个 token = 卡号 embedding(64) + 数值特征 7 维 MLP(64) + 预训练文本向量 96→64
  → 三者相加(64) → LayerNorm → 投影到 d=128
- 再加：6 个 zone embedding + 47 个位置 embedding + 全局特征 10 维广播
- 主干：**2 层 pre-norm Transformer**（4 头，ff=256，d=128）
- 池化：掩码平均 → 价值头；另六个任务头（play 12 / blk 30 / att 6 / evo 5 / act 29 / sel 8）

| 指标 | 数值 |
|---|---|
| 参数量 | **468,104**（含冻结的卡牌文本矩阵 49,920） |
| 张量数 | 63 |
| 权重文件 | `models/noosphere_weights.json` **2.38 MB**（gzip 后 **1.70 MB**） |
| 推理代码 | `js/noosphere.js` 21 KB（UMD，浏览器/Node 通用）+ `js/38-noosphere.js` 4.1 KB（加载器） |

文本分支用的是「卡牌语义预训练」产物，但阶段 A 的结论是：**语义没兑现出可测量的价值**，
真正起作用的是卡号 embedding + 数值特征 + 局面结构。所以文本矩阵可以视为冻结的先验，不是卖点。

## 4. 训练

- **数据**：5,116 局真人 v3 日志 → 648,710 可训练行（真人侧 336,633），加自对弈池后
  训练集 1,040,426 行 / 验证集 114,618 行。标签是「真人当时选了什么」。
- **流程**：分片接力（A 半 450k 训 10 轮 → 权重初始化 B 半 590k 训 12 轮），cosine 学习率，
  play 权重 2.0、value 权重 0.2、sel 权重 0.5，按验证一致率存最优轮。
- **当前上线版**：在 cos2B 基础上用 `--att-player-weight 3` 微调 6 轮（专门纠正「该打脸时打脸」）。
- **硬件**：A10 上全量 1.04M 行 43–90 秒/轮；CPU 上 144 行/秒（约 2 小时/轮，不实用）。

## 5. 效果（2026-10-23 实测）

**分头准确率（验证集，18290 条有真人标签的行）**

| 头 | play | att | blk | evo | act | sel | value(Brier) | 其中「打玩家」 |
|---|---|---|---|---|---|---|---|---|
| apw3 | 73.4% | 79.1% | 77.3% | 57.8% | 70.7% | 32.7% | 0.1845 | **99.1%** |
| 随机基线 | 18.9% | 74.5% | 66.6% | 37.0% | 12.6% | 12.5% | — | — |

**决策级对比（同一批人类局面，看「和真人选得一样吗」）**

| 场景 | 样本 | 引擎启发式 | Noosphere | 差值 |
|---|---|---|---|---|
| 出牌 | 3000 | 39.1% | **72.8%** | **+33.7pp ±1.8pp** |
| 攻击目标（角色） | 1200 | **84.3%** | 82.8% | −1.5pp ±2.8pp |
| 真人选「打玩家」的局面 | 6185 | 68.1%（硬规则） | **99.3%** | +31.2pp |

**整局强弱（魔搭 8 核，A/B 同种子配对，按种子块聚类统计）**

| 数据集 | 块数 | 每块局数 | 块均值差 |
|---|---|---|---|
| C（完赛制 3000 步） | 8 | 40 | +4.6pp |
| D（完赛制 6000 步） | 8 | 40 | +15.4pp |
| V（超时判定制） | 8 | 40 | +2.5pp |
| W（超时判定制，32 块） | 32 | 20 | +3.6pp |
| **全部合并** | **56** | — | **+5.3pp ±2.8pp（显著）** |

逐局合并口径：A 基线 49.2%（315/640 等批次合计）vs B 模型 52.8%，差 +3.6~+7.9pp 视批次而定。

> ⚠️ 早期版本曾写「+11.9pp ±7.2pp 显著」，那是用**逐局独立**假设算的区间，偏乐观。
> 同一种子块内的对局彼此相关，按块聚类后结论修正为 **+5.3pp ±2.8pp**（56 块）。详见
> `ENGINE_INTEGRATION.md` 的「统计口径修正」一节。

**P1 blk 阻挡头边际（2026-10-23，64 种子块 × 20 局/组 A/B/C）**：C−B = **−0.00pp ±1.62pp**
——阻挡头不增不减胜率，价值在拟人度（生产合法集合口径一致率 77.1% vs 引擎「几乎从不阻挡」67.4%）。

配对侧别验证（模型分别接管我方/对方，各 4 块）：我方 42.9%→55.8%、对方 57.1%→66.7%，
两侧方向一致（这两组各只有约 50 个完成局，区间宽，仅作方向性参考）。

**短板**：`evo`（进化）57.8%、`sel`（选择链）32.7% 明显偏弱，这两个头暂未接入生产。

## 6. 怎么部署

三个文件 + 两行 `<script>`（生产已按此上线）：

| 文件 | 大小 | 说明 |
|---|---|---|
| `js/noosphere.js` | 21 KB | 推理核心（UMD） |
| `js/38-noosphere.js` | 4.1 KB | 加载器：拉权重、装钩子、按侧别门控、状态缓存 |
| `models/noosphere_weights.json` | 2.38 MB（gzip 1.70 MB） | 权重（63 个 float32 张量，base64） |

```html
<script src="js/noosphere.js?v=1"></script>
<script src="js/38-noosphere.js?v=1"></script>
```

**开关**（默认关闭，不影响任何现有玩家）：
- URL `?noo=1` 打开；`?noo=0` 关闭
- 或 `localStorage.setItem('noosphere','1')` 持久开启
- 代码里也可以直接设 `window.NOOSPHERE_ENABLE = true`

**门控与缓存**：加载器默认只替 `opponent` 方打分（`__zsNooStats.side`）；
同一局面（含手牌/费区/场面/回合/侵蚀计数）只跑一次前向，一次行动内十几次打分基本命中缓存。

**换权重**：只替换 `models/noosphere_weights.json` 即可（本文件已验证 3.4e-6 与 PyTorch 对齐），
旧版留 `.bak-*` 备份。改 `noosphere.js` 则必须同时更新 index.html 的 `?v=`。

## 7. 配置要求

### 7.1 客户端（玩家浏览器 / Android WebView）

| | 最低 | 推荐 |
|---|---|---|
| 浏览器 | Chrome/Edge 90+、Safari 15+、Android WebView 90+（要 `Float64Array` + `fetch`） | 同左，保持最新版 |
| CPU | 2 核 1.6 GHz | 4 核 2.4 GHz+ |
| 可用内存 | **≥ 300 MB**（模型常驻约 50 MB：heap 20 MB + ArrayBuffer 27 MB） | 8 GB 整机内存 |
| 单次前向 | ≈ 100–200 ms（中端手机，实测桌面 33 ms 的 3–5 倍） | 20–35 ms（桌面，实测） |
| 首次加载 | 权重 1.7 MB（gzip），**建议走 CDN + 永久缓存**（`?v=` 版本号） | 同左 |
| 交互体验 | 一次 AI 回合 ~0.3–1.2 s（有状态缓存） | ~0.1–0.3 s |

**低端机建议**：只在「困难」难度启用，或按 `navigator.deviceMemory` / `hardwareConcurrency` 自动降级。
Android 壳打 APK 会把 1.7 MB 权重塞进包体（`assets` 里），包体敏感的渠道可以做「首次联网下载」。

### 7.2 服务端（批量评测 / 自对弈 / 数据生成）

| | 最低 | 推荐 |
|---|---|---|
| CPU | 1 vCPU（x86-64，Node 18+） | **8 vCPU**（可跑 8 分片并行） |
| 内存 | 512 MB 单进程（实测 300 MB，含 jsdom DOM） | 8 GB（8 进程 × ~300 MB，峰值 2.4 GB） |
| GPU | 不需要（纯 JS） | 不需要 |
| 吞吐（实测） | 单进程 7–20 s/局 | 8 分片并行：**640 局 / 5–10 分钟** |
| 磁盘 | 200 MB（代码 + 权重 + jsdom） | 5 GB（含训练数据） |

> 评测机实测：魔搭 A10 实例（8 核 28 GB）单局 8.7 s（harness 优化前）→ 优化后 8 分片跑 640 局约 5–10 分钟；
> HK VPS（8 逻辑核 Xeon E5）单进程 20–25 s/局。**A10 GPU 对纯 JS 推理没有任何帮助**，用的是 CPU 核。

### 7.3 训练（要重训才需要）

| | 最低（不推荐） | 推荐 |
|---|---|---|
| GPU | 无 GPU 也可：CPU 约 2 小时/轮（1.04M 行） | 单卡 ≥ 8 GB 显存（A10 43–90 s/轮） |
| 内存 | 16 GB | 32 GB |
| 磁盘 | 3 GB（训练数据 1.5 GB + 权重 20 MB + 缓存） | 20 GB |
| 时间 | 全流程（接力 + 微调）约 40 分钟 | 实验矩阵可一晚跑 20+ 组 |

## 8. 什么时候别用它

- **流量/包体敏感**：1.7 MB 权重 + 首屏 25 KB JS，对纯静态小游戏算重。
- **需要完全确定性的回放**：模型是浮点推理，跨设备可能有极小差异（对拍 3.4e-6 级），
  回放一致性要求极高的场景（例如仲裁系统）宁可保留启发式。
- **低端机为主的分发渠道**：中端手机单次前向 100–200 ms，AI 回合会明显变慢。
- **想让它理解卡面效果**：它看不懂效果文本，别指望它处理复杂连锁。

## 9. 运维与回滚

- **关闭**：去掉 `?noo=1` / 清 localStorage / 把 `window.NOOSPHERE_ENABLE = false`。
  钩子不存在时引擎行为与上线前完全一致（已验证）。
- **回滚权重**：`models/noosphere_weights.json.bak-cos2B`（上一版），cp 回去即生效（无构建步骤）。
- **健康检查**：`curl -sI https://<域名>/models/noosphere_weights.json` 应 200 且 ~2.5 MB；
  浏览器控制台看 `window.__zsNooStats`（`side`、`calls`、`weights`）。
- **发版纪律**：先备份 → /tmp 暂存 → 逐文件 SHA256 比对一致 → 才 `mv` 落盘（本项目已固化）。

## 10. 复现与文件清单

```bash
# 数值对拍（JS 推理 vs PyTorch 参考）
node scripts/noosphere_verify.js          # 期望：最大绝对误差 < 1e-3

# 决策级 A/B（选牌/攻击目标与真人一致率）
node scripts/ab_decision.js
node scripts/ab_attack.js

# 整局 A/B（无头自对弈，aiBoth）
ZS_MAX_STEPS=6000 ZS_MAX_TURNS=120 NOO_SEED_BASE=13000000 node scripts/ab_noosphere.js 40 play

# 侧别验证（模型换到对方侧）
ZS_MODEL_SIDE=opponent ZS_MAX_STEPS=6000 NOO_SEED_BASE=13000000 node scripts/ab_noosphere.js 20 play

# 部署开销实测
node scripts/_bench_deploy.js
```

| 文件 | 作用 |
|---|---|
| `js/noosphere.js` | 推理核心（前向、卡编码缓存、序列压缩） |
| `js/38-noosphere.js` | 浏览器加载器（开关、钩子、门控、状态缓存） |
| `models/noosphere_weights.json` | 权重（63 张量 / 468,104 参数） |
| `js/13-ai.js` | 引擎 AI，两个钩子接入点（feature flag，默认 inert） |
| `tcg-ai/model.py` / `train_bc.py` | 结构与训练（PyTorch） |
| `tcg-ai/export_weights.py` | PyTorch → JSON 导出 |
| `tcg-ai/export_reference.py` + `scripts/noosphere_verify.js` | 数值对拍 |
| `scripts/ab_decision.js` / `ab_attack.js` / `ab_noosphere.js` | 决策级 / 整局 A/B |
| `scripts/selfplay_harness.js` | 无头自对弈框架（jsdom + 虚拟时钟） |
| `tcg-ai/ENGINE_INTEGRATION.md` | 接入细节与全部实验数据 |

---

**已知限制速查**：`evo`/`sel` 两个头弱；不理解卡面效果文本；「打玩家」决策与引擎硬规则冲突
（引擎规则 68.1% vs 模型 99.3%，启用钩子后 AI 会明显更爱打脸——这是设计选择，不是 bug）；
整局对照仍有 ~40% 对局因步数/回合上限未终局。
