期望锚点' +
+ (it.ans ? "" + esc(it.exp) + "" : "无(未知题,考察拒答)") + "
' + esc(it.reply || "(空)") + "
" + + '" + + "commit 1da4b14c19175fd88f5ca0172ff95f3e6c2e5162 Author: WpyQwq <3911625973@qq.com> Date: Sat Sep 19 11:21:45 2026 +0800 Initial commit: 自然记忆站:12 页纯标准库 Python 静态站点生成器,含真实评测台账(108 条)与全站客户端检索 diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..608b237 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,230 @@ +# DESIGN.md — Natural Memory 站点重设计 + +> 这份文件是**设计动作的产物**,写在动效之前。依据是 `motion-web` skill: +> `page-design.md`(静态帧怎么设计)、`design-slop.md`(AI 味闸门)、`data/visual-bank.json`(可选原型)、 +> `motion-tokens.md`(时长/缓动/弹簧)、`choreography-arc.md`(叙事弧)。 + +--- + +## 0. 先测量,再设计:旧版站点的问题(实测,不是印象) + +在 `assets/_gate.html` 里按 `design-slop.md` + `page-design.md §2` 的可量化条款跑了一遍旧站(1576px,12 页): + +| 闸门 | 获奖站中位数(n=12) | 旧站实测 | 判定 | +|---|---|---|---| +| 最大渲染字号 | **108 px**(52–218) | **48 px** | ❌ | +| 正文字号 | 16 px(12–16) | 12–14 px | ✅ | +| **display ÷ body** | **≈ 8×**(3.3–14×) | **3.4–4.0×** | ❌ A6 | +| 主色数量 | **4**(1–6) | 6–7 | ❌ A4 | +| 渐变 | **0 / 15** | home 2、results 10 | ❌ A4 | +| body 全局 max-width | 15/15 无 | 无 | ✅ | +| `
` 一直没有显式 `font-family`,掉回 UA 样式表的 `monospace` ——
+**全站代码块此前都不是 Geist Mono**。这也是「字体家族 = 3」的来源。已修。
+
+**唯一保留的渐变**:`.bar--base .bar__fill` 用的是 `repeating-linear-gradient` 斜纹,
+它编码的是「无记忆基线」这一条数据,是数据墨水不是装饰(A4 针对的是渐变**背景处理**)。
+
+---
+
+## 7. 明确的偏差与代价(不藏)
+
+1. **移动端的字阶落差改用「数字」承担 —— 这一条已经解决,不是遗留问题。**
+ 中文标题在 320px 上推不到 7×:`page-design.md §4` 自己写了
+ 「weight contrast carries hierarchy better than size contrast alone, because a huge 黑体 becomes a solid block」——
+ 320px 宽放 98px 的中文标题,一行只有 3 个字,13 个字的标题要断成 5 行,那是排版事故。
+ 所以**每个证据页的页眉由一个真实数字领起**(`.doc__figure` + `.mega`),
+ 中文标题负责可读性,数字负责落差。实测结果:
+
+ | 视口 | 12 页 ratio 最小值 | ≥ 7× 的页数 | 布局问题 |
+ |---|---:|---:|---:|
+ | 1600 px | **11.1×** | 12 / 12 | 0 |
+ | 768 px | **7.8×** | 12 / 12 | 0 |
+ | 390 px | **7.1×** | 12 / 12 | 0 |
+ | 320 px | **7.1×** | 12 / 12 | 0 |
+ | 1600 px + reduced-motion | **11.1×** | 12 / 12 | 0 |
+
+ 这不是把数字调到达标,而是**让每一页自己先把最强的那条数字说出来** ——
+ 这些页面本来就是证据页,页眉只有一个标题反而浪费了它们最好的东西。
+2. **地址空间装置在 `≤900px` 与 `prefers-reduced-motion` 下不下降**,冻结成一帧静态地址空间
+ (窄屏冻结在 t=0.30「Qwen hidden state」,reduced-motion 冻结在 t=0.52「候选页」)。
+ 页面高度同时从 340vh 收到 1 屏,不留下 3 屏的空滚动。
+3. **`--body` 用 `#9a9a92` 而不是 p-07 原型写的 `#909090`** —— 为了 12 页技术文档的连续阅读,
+ 在原型值上抬了一档(对比度 7.6:1,AAA 正文标准以上)。这是对原型的**唯一**颜色偏离,特此标明。
+
+---
+
+## 8. 动效闸门自检结果(B1–B8)
+
+| 闸门 | 实测 | 处置 |
+|---|---|---|
+| B1 统一上浮 hover | `translateY(-*)` 命中 **0** | ✅ |
+| B2 全页统一 fade-up | 原本 ~20 个元素同款入场 → 现在 **3 处,三种不同手势**(擦除 / 数字冲入 / 侧向错峰)+ 路径图自己的画线 | ✅ |
+| B3 一个缓动一个时长 | 原本 5 个时长令牌有 3 个没用、**15 处硬编码 `120ms ease-out`** → 现在 **23 条 transition 全部走令牌**,实际用到 fast/std/med/slow **4 档** | ✅ |
+| B4 预算全烧在首屏 | 首屏是装置,但 ~60% 处另有头号数字、重启装置两处收网 | ✅ |
+| B5 动效与内容无关 | 机制来自「寻址」本身的物理,换到别的题材讲不通 | ✅ |
+| B6 只有视差做纵深 | 无视差图层 | ✅ |
+| B7 跟随光标的圆圈 | 无 | ✅ |
+| B8 reduced-motion 下什么都不剩 | 实测 `wipeTarget=none`、装置冻结成完整静态帧、页高收到 1 屏、12 页 0 布局问题 | ✅ |
+
+**顺带修掉的三个健壮性问题**(都不是猜的,是量出来的):
+
+1. **`` 没有 `font-family`** → 掉回 UA 的 `monospace`,**全站代码块此前都不是 Geist Mono**。
+2. **入场只靠 `IntersectionObserver`** → IO 在被节流的 iframe / 无头虚拟时间 / 某些低端 WebView 里不保证投递;
+ 而 `clip-path` 擦除一旦不触发就是**一个永远看不见的标题**。已加「按滚动位置判定」的兜底,实测未入场元素从 **5 → 0**。
+3. **首屏循环 latch**:`raf` 句柄非空但帧被冻结时,`kick()` 永远提前返回,循环永久卡死。
+ 已改为超时同步走一帧 —— 实测 `spaceT` 从 **0.00 → 0.39**。
+
+
+---
+
+## 1. 叙事结构(macrostructure)
+
+**Editorial + set-pieces** —— 页面主体是**可读的正文**,动效出现 3 次、每次都有明确含义,
+是段落之间的标点,不是背景装饰。
+
+选它的理由来自内容本身:这是一个**研究发布站**,主体是 12 页文档、数据表和一份 108 条台账。
+把它硬塞进「单场景」或「Acts」会牺牲可读性;而纯粹的 Editorial 又浪费了这套内容里
+**唯一真正有空间感的东西 —— 记忆的地址空间**。
+
+- **线性内容** → 走 editorial,而不是 index / instrument(`page-design.md §1` 的两条判据之一)。
+- **相机移动还是世界移动**:世界移动(地址空间被镜头穿过),所以镜头更像「下降」而不是「横摇」。
+
+**叙事弧:`cold-open`**(安静入场 → 复杂度上升 → 峰值 → 收束)
+`0–20%` 极简、只有字;`20–70%` 地址空间下降(峰值在此);`70–100%` 结果与边界,留白放开。
+
+---
+
+## 2. 视觉系统(提交一个原型,不混搭)
+
+来自 `data/visual-bank.json`,**选一个、执行到底**:
+
+- **调色:`p-07 Bold Black + Signal Accent`**("creative agencies, award sites, experimental portfolios, bold studios")
+ - ground `#000000`(纯黑,不是近黑)
+ - ink `#fffef4`(骨白,不是纯白)
+ - ink-2 `#909090` / ink-3 `#555555`
+ - surface `rgba(255,255,255,.05)` · border `rgba(255,255,255,.12)`
+ - **信号色 `#00ff6a`,每页只出现一次**(原型原文:one per page max)
+ - 主色数量从 6–7 压到 **≤4**;**渐变全部删除**
+- **字体:两个家族**(获奖站中位数就是 2,range 1–4)
+ - `Archivo Variable`(wght 100–900 · wdth 62–125%)—— 显示与正文的拉丁部分、**全部数字**
+ - `Geist Mono` —— 标签、代码、字段名
+ - 中文回落到系统 CJK(PingFang SC / 微软雅黑 / Noto Sans CJK),**不额外加载 CJK 字体**
+
+### 2.1 字阶:落差就是设计(`page-design.md §4`)
+
+| 令牌 | 值 | 用途 |
+|---|---|---|
+| `--fs-mega` | `clamp(4.5rem, 12vw, 12.5rem)` = 72 → **200 px** | 拉丁/数字巨字 |
+| `--fs-display` | `clamp(2.75rem, 7vw, 6.5rem)` = 44 → **104 px** | 页面主标题 |
+| `--fs-h2` | `clamp(1.75rem, 3.4vw, 3rem)` | 章节标题 |
+| `--fs-lede` | `1.125rem` | 导语 |
+| `--fs-body` | `0.875rem` | **正文(14 px)** |
+
+`mega ÷ body = 200/14 ≈ 14×`(获奖站上沿),`display ÷ body ≈ 7.4×`(≈ 中位数 8×)。
+旧站是 3.4× —— **这是本次重设计的核心动作**。
+
+### 2.2 中日韩显示字的特殊处理(`page-design.md §4` 明确点名)
+
+中文大字和拉丁大字**不是一套参数**:
+
+| | 行高 | 字距 | 字重 |
+|---|---|---|---|
+| 拉丁/数字 `.dsp--lat` | 0.90 | `-0.045em` | 700,`font-stretch: 92%` |
+| 中文 `.dsp` | **1.28** | **-0.005em** | 500 |
+
+中文行高更大、负字距要收(不然字腔会闭合),层级**靠字重对比**而不是只靠字号。
+所以拉丁与中文的 display 是两个 class,在标记里显式指定,不靠回落碰运气。
+
+---
+
+## 3. 版面(layout)
+
+- **全幅页面,逐元素设 measure**(获奖站 15/15 都没有 body 全局 max-width):
+ `--measure: 62ch` 用于连续正文,`--measure-wide: 88ch` 用于说明,媒体全幅。
+- **间距不是常数**(`page-design.md §6`):`--band-tight 48–80px` / `--band 80–160px` /
+ `--band-wide 120–260px`,按「两块该不该被读成一组」来给。
+- **至少三种不同的 section 形状**:全幅巨字带 / 非对称 2:1 分栏 / 数据表带 / 卡片网格 /
+ 装置带(整屏交互)/ 引用带 —— 不允许「每段都是居中标题 + 段落」。
+
+---
+
+## 4. 动效概念(来自题材自身的物理)
+
+**概念名:地址空间下降(Address Descent)**
+
+题材的物理就是**寻址**:1,048,576 个槽位、738 条活跃记录、730 个 LSH 桶、
+每页 32 条、每次只把少量记录提升到 GPU。整条读取路径本来就是**一次下降**:
+问题 → 128 维地址 → 粗索引桶 → 候选页 → 页内重排 → Top-K → 证据前缀 → 生成。
+
+**招牌场景**:一个整屏的地址空间栅格(1,048,576 个槽位画成点阵,738 个亮着)。
+滚动时镜头**一路下降五个数量级**:整个空间 → 730 个桶 → 一页 32 条 → 一条记录 →
+这条记录变成注入模型的前缀 token。终点是**一条记录**,不是一句 slogan。
+
+这个机制满足 `design-slop.md` B5(机制必须来自题材自身的物理):换到任何别的题材上都讲不通。
+
+**动效令牌**(`motion-tokens.md`,至少三档时长 + 弹簧):
+
+| 令牌 | 值 | 用途 |
+|---|---|---|
+| `--dur-fast` | 160ms | 悬停、按下、开关 |
+| `--dur-std` | 300ms | 面板、抽屉、状态切换 |
+| `--dur-med` | 460ms | 区块入场(少数几处) |
+| `--dur-slow` | 700ms | 巨字入场 |
+| `--dur-cine` | 1200ms | 冷开场 |
+| `--ease-exp` | `cubic-bezier(.16,1,.3,1)` | 默认出场 |
+| `--ease-sharp` | `cubic-bezier(.65,0,.45,1)` | 硬切、品牌感 |
+| `--ease-in-expo` | `cubic-bezier(.7,0,.84,0)` | **只用于离场** |
+
+**明确不做什么**(对应 `design-slop.md` B1–B8):
+- B1 不做「所有卡片统一上浮 4px + 阴影」
+- B2 不做「每个区块都 fade-up 20px」—— 大多数内容**本来就在那儿**,入场只留给 3 处
+- B3 不允许全站一个 `0.3s ease`
+- B4 不把预算全烧在首屏
+- B7 不做跟随光标的圆圈
+- B8 reduced-motion 下必须仍然是一张完整的页面
+
+---
+
+## 5. 移动端降级(`page-design.md §7`,显式写明,不含糊)
+
+- **地址空间装置**:`< 900px` 关闭点阵与镜头下降,改为一帧静态的地址空间图 + 数字条。
+ 降级**不是**隐藏内容,是把动效换成静态帧。
+- 巨字:`12vw` 下限 72px,320px 宽时仍是 72px(不缩到看不清)。
+- 悬停不存在 —— 所有悬停信息必须有第二个落点(见设备页/台账页的显式状态)。
+- 滚动在移动端更长,节奏按移动端重读一遍,不假设等比缩放。
+
+---
+
+## 6. 设计闸门自检(交付前必须全过)
+
+`design-slop.md` A1–A8 / B1–B8 + `page-design.md §8`,用 `assets/_gate.html` 逐条量:
+
+- [ ] A1 首屏不是「徽标 + 标题 + 副标题 + 两个按钮」
+- [ ] A2 没有三张等宽居中卡片
+- [ ] A3 每页 ≥3 种不同 section 形状
+- [ ] A4 无紫/靛渐变;主色 ≤4;信号色每页只出现一次
+- [ ] A5 没有噪点/光晕/网格纸这类「填空装饰」
+- [ ] A6 display ÷ body ≥ 7×
+- [ ] A7 没有手绘的假浏览器/假手机外壳
+- [ ] A8 没有编造的证明(本站所有数字都来自工程内真实报告)
+- [ ] B1–B8 动效闸门
+- [ ] D 移动端 320/375/414/768 无横向滚动;reduced-motion 下页面完整
diff --git a/assets/css/site.css b/assets/css/site.css
new file mode 100644
index 0000000..7168612
--- /dev/null
+++ b/assets/css/site.css
@@ -0,0 +1,1448 @@
+/* ==================================================================
+ Natural Memory — site.css
+
+ 设计系统见项目根目录 DESIGN.md(设计动作的产物,写在动效之前)。
+ 本文件按 motion-web skill 重建:
+
+ 调色 data/visual-bank.json → p-07「Bold Black + Signal Accent」
+ 纯黑地面 #000000 · 骨白 #fffef4 · 次级 #9a9a92 · 三级 #5c5c56
+ 信号色 #00ff6a —— 每页只出现一次(原型原文:one per page max)
+ 渐变全部删除(获奖站 15/15 都不用渐变做背景)
+
+ 字体 两个家族(获奖站中位数 2):
+ Archivo Variable(wght 100–900 · wdth 62–125%)—— 显示、正文拉丁、全部数字
+ Geist Mono —— 标签、代码、字段名
+ 中文回落系统 CJK,不额外加载 CJK 字体
+
+ 字阶 落差就是设计(page-design.md §4):
+ mega 200px / display 104px 对 body 14px = 14× / 7.4×
+ 中文大字与拉丁大字参数不同(§4 明确点名):
+ 拉丁 .dsp--lat lh .90 ls -.045em 700 stretch 92%
+ 中文 .dsp lh 1.28 ls -.005em 500
+
+ 版面 全幅页面、逐元素 measure(获奖站 15/15 都没有 body 全局 max-width)
+ 间距不是常数:tight / normal / wide 三档按语义给
+ ================================================================== */
+
+/* ---------- 1. 字体 ---------- */
+@font-face {
+ font-family: "Archivo Variable"; font-style: normal; font-weight: 100 900;
+ font-stretch: 62% 125%; font-display: swap;
+ src: url("../fonts/archivo-latin-wdth-normal.woff2") format("woff2-variations");
+}
+@font-face {
+ font-family: "Geist Mono"; font-style: normal; font-weight: 100 900; font-display: swap;
+ src: url("../fonts/geist-mono-latin-wght-normal.woff2") format("woff2-variations");
+}
+
+/* ---------- 2. 令牌 ---------- */
+:root {
+ /* 颜色 · p-07 —— 三个灰 + 一个信号色,不再有别的色相 */
+ --canvas: #000000;
+ --canvas-soft: #0e0e0e; /* 悬停/激活面 */
+ --canvas-card: #000000; /* 卡片与地面同色:层次只靠发丝线 */
+ --canvas-mid: #262623; /* 轨道底 */
+ --hairline: #1f1f1c;
+ --btn-border: rgba(255, 255, 255, .28);
+ --ink: #fffef4; /* 骨白,不是纯白 */
+ --ink-hover: #ffffff;
+ --on-primary: #000000;
+ --body: #9a9a92;
+ --mute: #5c5c56;
+ --signal: #00ff6a; /* 每页只用一次:首屏画布里那颗目标记录 */
+
+ /* 圆角:p-07 是硬边原型,圆角压到最小 */
+ --r-sm: 2px;
+ --r-pill: 9999px;
+
+ /* 间距 —— 4px 基数 */
+ --sp-xxs: 2px;
+ --sp-xs: 4px;
+ --sp-sm: 8px;
+ --sp-md: 12px;
+ --sp-lg: 16px;
+ --sp-xl: 24px;
+ --sp-2xl: 32px;
+ --sp-3xl: 48px;
+ --sp-4xl: 64px;
+
+ /* 版式 */
+ --font-sans: "Archivo Variable", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", system-ui, sans-serif;
+ --font-mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace;
+
+ /* 字阶 —— mega ÷ body = 14×,display ÷ body = 7.4× */
+ --fs-mega: clamp(72px, 12vw, 200px);
+ --fs-display: clamp(44px, 7vw, 104px);
+ --fs-display-xl: var(--fs-display);
+ --fs-display-lg: clamp(32px, 4.6vw, 64px);
+ --fs-display-md: clamp(26px, 3.2vw, 44px);
+ --fs-display-sm: 24px;
+ --fs-display-xs: 18px;
+ --fs-lede: 1.125rem;
+ --fs-body: 0.875rem; /* 14px */
+ --fs-small: 0.875rem; /* 连续正文:不为了「看起来精致」压到 13px */
+ --fs-label: 0.75rem; /* 12px,标签/注释/字段名 */
+
+ --lh-lat: .90; /* 拉丁巨字 */
+ --lh-cjk: 1.28; /* 中文巨字:行高更大、负字距要收 */
+ --ls-lat: -.045em;
+ --ls-cjk: -.005em;
+ --ls-display: var(--ls-cjk);
+ --font-stretch-display: 92%;
+
+ /* 版面 */
+ --container: 1440px; /* 内层栅格上限,不是 body 的 max-width */
+ --measure: 62ch;
+ --measure-wide: 88ch;
+ --nav-h: 66px;
+
+ /* 段落留白 —— 三档,按「两块该不该被读成一组」给 */
+ --band-tight: clamp(48px, 6vw, 80px);
+ --band: clamp(80px, 10vw, 152px);
+ --band-wide: clamp(120px, 16vw, 248px);
+
+ /* 动效令牌(motion-tokens.md) */
+ --dur-fast: 160ms;
+ --dur-std: 300ms;
+ --dur-med: 460ms;
+ --dur-slow: 700ms;
+ --dur-cine: 1200ms;
+ --ease-exp: cubic-bezier(.16, 1, .3, 1);
+ --ease-sharp: cubic-bezier(.65, 0, .45, 1);
+ --ease-circ: cubic-bezier(0, .55, .45, 1);
+ --ease-in-expo: cubic-bezier(.7, 0, .84, 0);
+ --ease: var(--ease-exp); /* 兼容旧引用 */
+}
+
+/* ---------- 3. 基础 ---------- */
+*, *::before, *::after { box-sizing: border-box; }
+html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
+
+body {
+ margin: 0;
+ background: var(--canvas);
+ color: var(--body);
+ font-family: var(--font-sans);
+ font-size: var(--fs-body);
+ line-height: 1.7;
+ font-weight: 400;
+ letter-spacing: 0;
+ font-synthesis-weight: none;
+ text-wrap: pretty;
+ -webkit-font-smoothing: antialiased;
+ overflow-x: hidden;
+}
+
+a { color: inherit; text-decoration: none; }
+button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
+::selection { background: var(--ink); color: var(--on-primary); }
+:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }
+
+/* 行内代码 */
+code {
+ font-family: var(--font-mono);
+ font-size: 0.9em;
+ color: var(--ink);
+ background: var(--canvas-soft);
+ padding: 1px 5px;
+ border-radius: var(--r-sm);
+}
+/* pre 必须显式指定:UA 样式表给的是 font-family: monospace,
+ 不覆盖的话代码块会掉回浏览器默认等宽字体(此前全站就是这样)。 */
+pre {
+ margin: 0;
+ font-family: var(--font-mono);
+ font-size: var(--fs-label);
+ line-height: 1.75;
+ tab-size: 2;
+}
+pre code { background: none; padding: 0; font-size: inherit; border-radius: 0; }
+
+.wrap { width: 100%; max-width: var(--container); margin-inline: auto; padding-inline: clamp(20px, 4vw, 56px); }
+.wrap--narrow { max-width: none; }
+.skip { position: absolute; left: -9999px; }
+.skip:focus {
+ left: var(--sp-lg); top: var(--sp-lg); z-index: 99;
+ background: var(--ink); color: var(--on-primary);
+ padding: var(--sp-sm) var(--sp-lg);
+}
+
+/* ---------- 4. 排版:落差就是设计 ---------- */
+h1, h2, h3, h4 { margin: 0; font-family: var(--font-sans); font-weight: 500; color: var(--ink); }
+
+/* 中文大字:行高放大、负字距收回,层级靠字重(page-design.md §4) */
+.dsp {
+ font-size: var(--fs-display); line-height: var(--lh-cjk);
+ letter-spacing: var(--ls-cjk); font-weight: 500;
+}
+/* 拉丁/数字巨字:同一量级但可以收紧 */
+.dsp--lat {
+ font-size: var(--fs-display); line-height: var(--lh-lat);
+ letter-spacing: var(--ls-lat); font-weight: 700;
+ font-stretch: var(--font-stretch-display);
+ font-variant-numeric: tabular-nums;
+}
+/* 超巨:只给真正的头号数字。数字是拉丁字形,可以推到 200px 而不变成一块黑体方块 ——
+ 这正好绕开 page-design §4 说的「中文大字过大就成实心块」的问题,
+ 所以移动端撑住 display ÷ body 落差的是它,不是中文标题。 */
+.mega {
+ font-size: clamp(84px, 26vw, 200px); line-height: .86; letter-spacing: -.05em;
+ font-weight: 700; font-stretch: 88%; color: var(--ink);
+ font-variant-numeric: tabular-nums; display: block;
+}
+.mega--cjk { font-size: var(--fs-display); line-height: var(--lh-cjk); letter-spacing: var(--ls-cjk); font-weight: 500; font-stretch: 100%; }
+
+.h1 { font-size: var(--fs-display); line-height: var(--lh-cjk); letter-spacing: var(--ls-cjk); }
+.h2 { font-size: var(--fs-display-md); line-height: 1.16; letter-spacing: -.012em; }
+.h3 { font-size: var(--fs-display-sm); line-height: 1.3; letter-spacing: -.008em; }
+.doc__h { font-size: clamp(56px, 10.5vw, 100px); line-height: 1.14; letter-spacing: var(--ls-cjk); }
+
+strong, b { font-weight: 600; color: var(--ink); }
+em { font-style: italic; }
+
+.lede, .lede2 { font-size: var(--fs-lede); line-height: 1.65; color: var(--body); max-width: var(--measure); }
+.small { font-size: var(--fs-small); line-height: 1.65; color: var(--body); max-width: var(--measure-wide); }
+.tiny { font-size: var(--fs-label); line-height: 1.6; color: var(--mute); max-width: 96ch; }
+.mono { font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 1.4px; }
+.src { font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 1.2px; color: var(--mute); }
+
+/* 签名式眉标:等宽大写 + 正字距 */
+.eyebrow, .kicker, .shead__idx {
+ font-family: var(--font-mono);
+ font-size: var(--fs-label);
+ line-height: 1.6;
+ font-weight: 400;
+ letter-spacing: 1.8px;
+ text-transform: uppercase;
+ color: var(--mute);
+ margin: 0;
+ display: block;
+}
+.arrow { color: var(--mute); padding-inline: 6px; }
+.rule { height: 1px; background: var(--hairline); border: 0; margin: 0; }
+.serif-it { color: var(--ink); }
+
+/* 正文区的小节节奏:统一用 48px 上留白,避免各页内联边距不一致 */
+#doc h2, #doc h3, #doc h4 { margin-top: var(--sp-3xl); }
+#doc > *:first-child, .card h2, .card h3, .card h4,
+.panel h2, .panel h3, .panel h4, .ex h3 { margin-top: 0; }
+#doc > p + h2, #doc > p + h3 { margin-top: var(--sp-3xl); }
+#doc > p { margin: 0 0 var(--sp-lg); }
+#doc > p.small, #doc > p.tiny { margin-bottom: var(--sp-md); }
+
+/* ---------- 5. 顶栏(nav-bar: padding 12 24) ---------- */
+.site {
+ position: sticky; top: 0; z-index: 50;
+ background: var(--canvas);
+ border-bottom: 1px solid transparent;
+}
+.site.is-stuck { border-bottom-color: var(--hairline); }
+
+.topbar {
+ display: flex; align-items: center; gap: var(--sp-lg);
+ height: var(--nav-h); max-width: var(--container); margin-inline: auto;
+ padding-inline: var(--sp-xl);
+}
+.topbar__spacer { flex: 1 1 auto; }
+
+.brand { display: flex; align-items: center; gap: var(--sp-sm); flex: none; }
+.brand__mark { width: 22px; height: 22px; flex: none; }
+.brand__mark .dim { fill: var(--canvas-mid); }
+.brand__mark .on { fill: var(--ink); }
+.brand__name { font-size: 14px; line-height: 20px; color: var(--ink); }
+
+/* 药丸:整个交互形状系统 */
+.pill, .chip, .kbd {
+ display: inline-flex; align-items: center; gap: var(--sp-sm);
+ border: 1px solid var(--btn-border);
+ border-radius: var(--r-pill);
+ color: var(--ink);
+ white-space: nowrap;
+}
+.pill { height: 28px; padding: 0 var(--sp-md); font-size: 12px; letter-spacing: 1.2px; font-family: var(--font-mono); }
+.pill--acc { border-color: var(--ink); }
+.dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ink); flex: none; }
+
+.crumb { display: flex; align-items: baseline; gap: var(--sp-sm); min-width: 0; }
+.crumb__n { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.crumb__t { font-size: 14px; line-height: 20px; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+
+.progress { position: absolute; left: 0; bottom: -1px; height: 1px; width: 0; background: var(--ink); }
+
+/* ---------- 6. 骨架:侧栏 + 内容(容器 1200px) ---------- */
+.shell {
+ display: grid; grid-template-columns: 232px minmax(0, 1fr);
+ gap: var(--sp-4xl);
+ max-width: var(--container); margin-inline: auto; padding-inline: var(--sp-xl);
+ overflow-x: clip;
+}
+.side {
+ position: sticky; top: var(--nav-h); align-self: start;
+ max-height: calc(100vh - var(--nav-h)); overflow-y: auto;
+ padding: var(--sp-2xl) 0 var(--sp-3xl);
+ border-right: 1px solid var(--hairline);
+}
+.side__in { padding-right: var(--sp-lg); }
+.side, .side__in, .side__list, .side__a { min-width: 0; }
+
+.side__grp {
+ margin: var(--sp-xl) 0 var(--sp-sm); padding-left: var(--sp-md);
+ font-family: var(--font-mono); font-size: 12px; line-height: 16px;
+ letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute);
+}
+.side__grp:first-child { margin-top: 0; }
+.side__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-xxs); }
+/* ex-app-shell-row:active 用 primary 作为指示条 */
+.side__a {
+ display: grid; grid-template-columns: 20px minmax(0, 1fr); gap: var(--sp-sm);
+ align-items: baseline; padding: var(--sp-sm) var(--sp-md);
+ border-radius: var(--r-sm); border-left: 1px solid transparent;
+ transition: background var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.side__a:hover { background: var(--canvas-soft); }
+.side__a.is-on { background: var(--canvas-soft); border-left-color: var(--ink); }
+.side__n { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.side__t { font-size: 14px; line-height: 20px; color: var(--body); }
+.side__a:hover .side__t { color: var(--ink); }
+.side__a.is-on .side__t { color: var(--ink); }
+.side__a.is-on .side__n { color: var(--ink); }
+
+/* hero / content band */
+.doc { position: relative; min-width: 0; max-width: 100%; padding: var(--band) 0 var(--band-tight); }
+/* 页眉:标题左、头号数字右 —— 非对称分栏,不是居中标题块 */
+.doc__hd {
+ display: grid; grid-template-columns: minmax(0, 1fr) auto;
+ gap: var(--sp-xl) clamp(24px, 5vw, 72px);
+ align-items: end;
+ margin-bottom: var(--band-tight); padding-bottom: var(--sp-xl);
+ border-bottom: 1px solid var(--hairline);
+}
+.doc__hd > .kicker { grid-column: 1 / -1; }
+.doc__h { margin-top: var(--sp-lg); }
+.doc__lede {
+ margin: var(--sp-xl) 0 0; max-width: var(--measure);
+ font-size: var(--fs-lede); line-height: 1.6; color: var(--body);
+}
+/* 头号数字:文档页自己先把最强的那条数字说出来 */
+.doc__figure { margin: 0; text-align: right; display: grid; gap: var(--sp-md); justify-items: end; }
+.doc__figure .mega { color: var(--ink); }
+.doc__figure .mega small { font-size: .30em; letter-spacing: -.01em; margin-left: .04em; font-weight: 500; }
+.doc__figure-n {
+ font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 1.2px;
+ color: var(--mute); max-width: 30ch; line-height: 1.8;
+}
+
+/* 文档页的章节标题不能停在 24px:页面标题 100px、正文 14px,
+ 章节标题落在中间会让层级整个塌掉。片段里写的是 class="h3",这里按上下文提上来。 */
+#doc h2 { font-size: var(--fs-display-md); line-height: 1.14; letter-spacing: -.012em; }
+
+@media (max-width: 900px) {
+ .doc__hd { grid-template-columns: minmax(0, 1fr); align-items: start; }
+ .doc__figure { text-align: left; justify-items: start; margin-top: var(--sp-xl); }
+ .doc__figure .mega { font-size: clamp(98px, 26vw, 140px); }
+}
+
+/* ---------- 7. 按钮:轮廓胶囊为常态,白色实底仅作极少数例外 ---------- */
+.btn {
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-sm);
+ padding: var(--sp-sm) var(--sp-lg);
+ background: transparent;
+ border: 1px solid var(--btn-border);
+ border-radius: var(--r-pill);
+ color: var(--ink);
+ font-size: 14px; line-height: 20px; font-weight: 400;
+ transition: background var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.btn:hover { background: var(--canvas-soft); border-color: var(--ink); }
+.btn--pri { background: var(--ink); border-color: var(--ink); color: var(--on-primary); }
+.btn--pri:hover { background: var(--ink-hover); border-color: var(--ink-hover); color: var(--on-primary); }
+.hero__cta { display: flex; flex-wrap: wrap; gap: var(--sp-md); align-items: center; }
+
+.chip { height: 28px; padding: 0 var(--sp-md); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--body); }
+.chip__dot { width: 5px; height: 5px; border-radius: 50%; background: var(--ink); }
+.kbd { min-width: 26px; height: 26px; padding: 0 var(--sp-sm); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--body); }
+
+/* ---------- 8. 卡片:8px 圆角 · #191919 · 发丝线 · 零阴影 ---------- */
+.grid { display: grid; gap: var(--sp-lg); }
+.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.g3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+.g-2-1 { grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr); }
+
+.card {
+ background: var(--canvas-card);
+ border: 1px solid var(--hairline);
+ border-radius: var(--r-sm);
+ padding: var(--sp-xl);
+}
+.card--flat { background: var(--canvas-card); }
+/* 极性翻转:白底近黑字,规范里的 featured/active 处理 */
+.card--acc { background: var(--ink); border-color: var(--ink); color: var(--on-primary); }
+.card--acc .eyebrow, .card--acc .h3, .card--acc strong, .card--acc .small, .card--acc .tiny { color: var(--on-primary); }
+.card--warn { background: var(--canvas-card); }
+
+/* ---------- 9. KPI / 数值(mono 计数器,规范里的 metric counter) ---------- */
+.kpis { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.kpi { background: var(--canvas-card); padding: var(--sp-xl); display: grid; gap: var(--sp-md); align-content: start; }
+.kpi__k { font-family: var(--font-mono); font-size: 12px; line-height: 16px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.kpi__v { font-family: var(--font-mono); font-size: 24px; line-height: 32px; color: var(--ink); letter-spacing: -.02em; }
+.kpi__v--long { font-size: 20px; line-height: 28px; }
+.kpi__v .to { color: var(--ink); }
+.kpi__v .mid { color: var(--mute); }
+.kpi__d { font-size: 14px; line-height: 20px; color: var(--mute); }
+
+.statrow { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.stat { background: var(--canvas-card); padding: var(--sp-xl); display: grid; gap: var(--sp-sm); align-content: start; }
+.stat__k { font-family: var(--font-mono); font-size: 12px; line-height: 16px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.stat__v { font-family: var(--font-mono); font-size: 28px; line-height: 32px; color: var(--ink); letter-spacing: -.02em; }
+.stat__v small { font-size: 12px; letter-spacing: 1.2px; color: var(--mute); margin-left: var(--sp-xs); }
+.stat--acc .stat__v { color: var(--ink); }
+.stat__d { font-size: 14px; line-height: 20px; color: var(--mute); }
+
+.mini { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.mini > div { background: var(--canvas-card); display: flex; justify-content: space-between; gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); }
+.mini span { font-size: 14px; line-height: 20px; color: var(--body); }
+.mini b { font-family: var(--font-mono); font-size: 14px; color: var(--ink); }
+.mini--lg > div { display: block; padding: var(--sp-lg); }
+.mini--lg span { font-size: 16px; line-height: 24px; }
+
+/* ---------- 10. 导航卡片 / 作者 ---------- */
+.navcard {
+ display: grid; gap: var(--sp-sm); align-content: start;
+ background: var(--canvas-card); border: 1px solid var(--hairline);
+ border-radius: var(--r-sm); padding: var(--sp-xl);
+ transition: background var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.navcard:hover { background: var(--canvas-soft); border-color: var(--btn-border); }
+.navcard__n { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.navcard__t { font-size: 20px; line-height: 28px; color: var(--ink); letter-spacing: -.01em; }
+.navcard__d { font-size: 14px; line-height: 20px; color: var(--mute); }
+.navcard--acc { border-color: var(--btn-border); }
+
+.authorcard { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-xl); }
+.authorcard__k { margin: 0 0 var(--sp-sm); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.authorcard__a { font-size: 16px; line-height: 24px; color: var(--ink); border-bottom: 1px solid var(--btn-border); }
+.authorcard__a:hover { border-bottom-color: var(--ink); }
+.authorcard__v { margin: 0; font-family: var(--font-mono); font-size: 14px; color: var(--body); }
+
+.author { display: grid; gap: var(--sp-sm); }
+.author__a { display: inline-flex; align-items: baseline; gap: var(--sp-sm); }
+.author__k { font-size: 14px; line-height: 20px; color: var(--mute); white-space: nowrap; }
+.author__v { font-family: var(--font-mono); font-size: 14px; color: var(--ink); border-bottom: 1px solid var(--btn-border); }
+.author__a:hover .author__v { border-bottom-color: var(--ink); }
+
+/* ---------- 11. 表格(ex-data-table-cell:表头 mono 大写、内容 body-sm) ---------- */
+.tbl-wrap { overflow-x: auto; border: 1px solid var(--hairline); border-radius: var(--r-sm); background: var(--canvas-card); }
+.dtable { width: 100%; border-collapse: collapse; }
+.dtable th {
+ text-align: left; background: var(--canvas-soft);
+ font-family: var(--font-mono); font-size: 12px; line-height: 16px; font-weight: 400;
+ letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute);
+ padding: var(--sp-md) var(--sp-lg); border-bottom: 1px solid var(--hairline); white-space: nowrap;
+}
+.dtable td { padding: var(--sp-md) var(--sp-lg); border-bottom: 1px solid var(--hairline); color: var(--body); font-size: 14px; line-height: 20px; vertical-align: baseline; }
+.dtable tr:last-child td { border-bottom: 0; }
+.dtable tr:hover td { background: var(--canvas-soft); }
+.dtable td.n { font-family: var(--font-mono); color: var(--ink); white-space: nowrap; }
+.dtable td.acc { color: var(--ink); }
+.dtable td.bad { color: var(--mute); }
+
+.gt { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.gt__h, .gt__r { display: grid; gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); background: var(--canvas-card); align-items: baseline; }
+.gt__h { background: var(--canvas-soft); }
+.gt__h span { font-family: var(--font-mono); font-size: 12px; line-height: 16px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.gt--4 .gt__h, .gt--4 .gt__r { grid-template-columns: minmax(0, .8fr) minmax(0, 1.1fr) minmax(0, .75fr) minmax(0, 1.5fr); }
+.gt--3 .gt__h, .gt--3 .gt__r { grid-template-columns: minmax(0, .75fr) minmax(0, 1fr) minmax(0, 1.95fr); }
+.gt__r:hover { background: var(--canvas-soft); }
+.gt__k { font-size: 16px; line-height: 24px; color: var(--ink); }
+.gt__v { font-size: 14px; line-height: 20px; color: var(--body); }
+.gt__who { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.gt__who--acc { color: var(--ink); }
+.gt__why { font-size: 14px; line-height: 22px; color: var(--mute); }
+.scrollx { overflow-x: auto; border-radius: var(--r-sm); }
+.scrollx > .gt { min-width: 720px; }
+
+/* ---------- 12. 键值 / 容量 ---------- */
+.kv { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.kv__r { background: var(--canvas-card); display: grid; grid-template-columns: 116px minmax(0, 1fr); gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); }
+.kv__k { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.kv__v { font-family: var(--font-mono); font-size: 14px; color: var(--body); word-break: break-all; }
+
+.cap { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.cap__r { background: var(--canvas-card); display: grid; grid-template-columns: minmax(0, 1fr) 168px; gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); align-items: baseline; }
+.cap__k { font-size: 16px; line-height: 24px; color: var(--body); }
+.cap__k small { display: block; color: var(--mute); font-size: 14px; line-height: 20px; }
+.cap__v { font-family: var(--font-mono); font-size: 16px; text-align: right; color: var(--ink); }
+.cap__r--hi .cap__v { color: var(--ink); }
+
+.pagemap { display: grid; grid-template-columns: repeat(64, 1fr); gap: 2px; }
+.pagemap i { aspect-ratio: 1; background: var(--canvas-mid); border-radius: 1px; }
+.pagemap i.on { background: var(--ink); }
+
+/* ---------- 13. 账本 / 显存 ---------- */
+.ledger { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.ledger__h, .ledger__r { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, .85fr) minmax(0, .85fr) minmax(0, .9fr); gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); align-items: baseline; }
+.ledger__h { background: var(--canvas-soft); }
+.ledger__h span { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.ledger__r { background: var(--canvas-card); }
+.ledger__r:hover { background: var(--canvas-soft); }
+.ledger__k { font-size: 16px; line-height: 24px; color: var(--body); }
+.ledger__b, .ledger__n { font-family: var(--font-mono); font-size: 14px; color: var(--mute); }
+.ledger__n { color: var(--ink); }
+.ledger__d { font-family: var(--font-mono); font-size: 14px; text-align: right; color: var(--ink); }
+.ledger__d.t-ok, .ledger__d.t-warn, .ledger__d.t-bad, .ledger__d.t-mute { color: var(--body); }
+.ledger__d.t-bad, .ledger__d.t-mute { color: var(--mute); }
+
+.vram { display: grid; gap: var(--sp-lg); }
+.vram__row { display: grid; gap: var(--sp-sm); }
+.vram__top { display: flex; justify-content: space-between; gap: var(--sp-md); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.vram__top b { color: var(--ink); }
+.vram__track { position: relative; height: 20px; background: var(--canvas-mid); border-radius: var(--r-pill); overflow: hidden; }
+.vram__fill { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: var(--r-pill); transition: width 700ms cubic-bezier(.16,1,.3,1); }
+.vram__fill--base { background: repeating-linear-gradient(115deg, var(--mute) 0 3px, transparent 3px 7px); }
+.vram__fill--nm2 { background: var(--ink); }
+.vram__mark { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--btn-border); }
+.vram__card { display: flex; justify-content: space-between; font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+
+/* ---------- 14. 条形对比(单色明度阶梯,不用彩色编码) ---------- */
+.bars { display: grid; gap: var(--sp-xl); }
+.barset { display: grid; gap: var(--sp-sm); }
+.barset__top { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--sp-sm); }
+.barset__t { font-size: 16px; line-height: 24px; color: var(--ink); }
+.barset__n { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+
+.bar { display: grid; grid-template-columns: 108px minmax(0, 1fr) 108px; gap: var(--sp-md); align-items: center; }
+.bar__l { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.bar__track { position: relative; height: 6px; background: var(--canvas-mid); border-radius: var(--r-pill); overflow: hidden; }
+.bar__fill { position: absolute; inset: 0 auto 0 0; width: 0; border-radius: var(--r-pill); transition: width 700ms cubic-bezier(.16,1,.3,1); }
+.bar--base .bar__fill { background: repeating-linear-gradient(115deg, var(--mute) 0 3px, transparent 3px 7px); }
+.bar--a .bar__fill { background: var(--mute); }
+.bar--rag .bar__fill { background: var(--mute); }
+.bar--nm2 .bar__fill { background: var(--ink); }
+.bar__v { font-family: var(--font-mono); font-size: 14px; color: var(--ink); text-align: right; }
+.bar__v small { color: var(--mute); display: block; font-size: 12px; letter-spacing: 1.2px; }
+
+.legend { display: flex; flex-wrap: wrap; gap: var(--sp-lg); }
+.legend span { display: inline-flex; align-items: center; gap: var(--sp-sm); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.legend i { width: 14px; height: 6px; border-radius: var(--r-pill); display: block; }
+.legend i.l-base { background: repeating-linear-gradient(115deg, var(--mute) 0 3px, transparent 3px 7px); }
+.legend i.l-a { background: var(--mute); }
+.legend i.l-rag { background: var(--mute); }
+.legend i.l-nm2 { background: var(--ink); }
+
+.gap { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.gap__c { background: var(--canvas-card); padding: var(--sp-xl); display: grid; gap: var(--sp-sm); }
+.gap__k { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.gap__v { font-family: var(--font-mono); font-size: clamp(28px, 4vw, 40px); line-height: 1; letter-spacing: -.02em; }
+.gap__c--recall .gap__v { color: var(--body); }
+.gap__c--answer .gap__v { color: var(--ink); }
+.gap__n { font-size: 14px; line-height: 20px; color: var(--mute); }
+
+/* ---------- 15. 标签页(药丸) ---------- */
+.tabs { display: flex; flex-wrap: wrap; gap: var(--sp-sm); max-width: 100%; }
+.tab {
+ padding: var(--sp-sm) var(--sp-lg);
+ border: 1px solid var(--btn-border); border-radius: var(--r-pill);
+ color: var(--body); font-size: 14px; line-height: 20px; white-space: nowrap;
+ transition: background var(--dur-fast) var(--ease-exp), color var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.tab:hover { background: var(--canvas-soft); color: var(--ink); }
+.tab.is-on { background: var(--ink); border-color: var(--ink); color: var(--on-primary); }
+.panel { display: none; }
+.panel.is-on { display: block; }
+
+/* ---------- 16. 机制:步进器 / 流程图 / 记录 ---------- */
+.flow { display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1.18fr); gap: var(--sp-2xl); align-items: start; }
+.steps { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.step {
+ background: var(--canvas-card); text-align: left; width: 100%;
+ display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: var(--sp-md);
+ padding: var(--sp-md) var(--sp-lg); align-items: center;
+ transition: background var(--dur-fast) var(--ease-exp);
+}
+.step:hover { background: var(--canvas-soft); }
+.step__i { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.step__l { font-size: 14px; line-height: 20px; color: var(--body); }
+.step.is-on { background: var(--canvas-soft); }
+.step.is-on .step__l { color: var(--ink); }
+.step.is-on .step__i { color: var(--ink); }
+.step.is-past .step__l { color: var(--mute); }
+
+.flow__detail { position: sticky; top: calc(var(--nav-h) + var(--sp-lg)); }
+.flow__stage { margin: 0 0 var(--sp-md); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.flow__title { margin: 0 0 var(--sp-md); font-size: 24px; line-height: 32px; letter-spacing: -.02em; }
+.flow__body { margin: 0; color: var(--body); font-size: 14px; line-height: 22px; min-height: 88px; }
+.flow__io { margin: var(--sp-lg) 0 0; padding-top: var(--sp-md); border-top: 1px solid var(--hairline); font-family: var(--font-mono); font-size: 12px; line-height: 20px; color: var(--mute); }
+.flow__meta { margin-top: var(--sp-lg); padding-top: var(--sp-md); border-top: 1px solid var(--hairline); display: grid; gap: var(--sp-sm); }
+.flow__meta div { display: flex; justify-content: space-between; gap: var(--sp-lg); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.flow__meta b { color: var(--ink); font-weight: 400; }
+
+.dgm { width: 100%; height: auto; display: block; overflow: visible; }
+.dgm__box { fill: var(--canvas-card); stroke: var(--hairline); }
+.dgm__box--acc { stroke: var(--btn-border); }
+.dgm__n { font-family: var(--font-mono); font-size: 9px; fill: var(--mute); letter-spacing: 1px; }
+.dgm__l { font-size: 12px; fill: var(--ink); }
+.dgm__s { font-family: var(--font-mono); font-size: 9px; fill: var(--mute); }
+.dgm__a { stroke: var(--btn-border); stroke-width: 1; fill: none; }
+.dgm__rule { stroke: var(--hairline); stroke-width: 1; }
+.dgm__note { font-family: var(--font-mono); font-size: 9.5px; fill: var(--mute); letter-spacing: .6px; }
+.dgm__note:first-of-type { fill: var(--ink); }
+
+.rec { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.rec__f { background: var(--canvas-card); padding: var(--sp-md) var(--sp-lg); display: grid; gap: var(--sp-xxs); }
+.rec__f:hover { background: var(--canvas-soft); }
+.rec__k { font-family: var(--font-mono); font-size: 14px; color: var(--ink); }
+.rec__d { font-size: 12px; line-height: 16px; color: var(--mute); }
+
+.recjson { display: grid; gap: 1px; }
+.recjson__r { display: grid; grid-template-columns: 116px minmax(0, 1fr); gap: var(--sp-md); padding: var(--sp-sm) 0; border-bottom: 1px solid var(--hairline); align-items: baseline; }
+.recjson__r:last-child { border-bottom: 0; }
+.recjson__k { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.recjson__v { font-size: 14px; line-height: 20px; color: var(--ink); word-break: break-word; overflow-wrap: anywhere; }
+
+.states { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.state { background: var(--canvas-card); display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: var(--sp-lg); padding: var(--sp-md) var(--sp-lg); align-items: baseline; }
+.state__s { font-family: var(--font-mono); font-size: 14px; color: var(--ink); }
+.state--mute .state__s, .state--bad .state__s { color: var(--mute); }
+.state--warn .state__s { color: var(--body); }
+.state__d { font-size: 14px; line-height: 20px; color: var(--body); }
+
+/* ---------- 17. 提示块 / 声明 / 局限 ---------- */
+.callout {
+ background: var(--canvas-card);
+ border: 1px solid var(--btn-border);
+ border-radius: var(--r-sm);
+ padding: var(--sp-xl);
+}
+.callout--warn { border-color: var(--hairline); }
+.callout__t { margin: 0 0 var(--sp-sm); font-family: var(--font-mono); font-size: 14px; line-height: 20px; letter-spacing: 1.4px; text-transform: uppercase; color: var(--ink); }
+.callout__b { margin: 0; font-size: 16px; line-height: 26px; color: var(--body); max-width: 78ch; }
+
+.claims { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.claim { background: var(--canvas-card); display: grid; grid-template-columns: 22px minmax(0, 1fr) auto; gap: var(--sp-md); padding: var(--sp-md) var(--sp-lg); align-items: center; }
+.claim:hover { background: var(--canvas-soft); }
+.claim__x { font-family: var(--font-mono); font-size: 14px; color: var(--mute); }
+.claim__t { font-size: 16px; line-height: 24px; color: var(--mute); text-decoration: line-through; text-decoration-color: var(--canvas-mid); }
+.claim__s { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.claims--loose .claim__s { display: none; }
+
+.lims { display: grid; gap: var(--sp-xl); }
+.lim { display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: var(--sp-md); }
+.lim__i { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); padding-top: 3px; }
+.lim__t { font-size: 20px; line-height: 28px; margin-bottom: var(--sp-sm); }
+.lim__d { margin: 0; font-size: 14px; line-height: 22px; color: var(--mute); max-width: 76ch; }
+
+.quote { border-left: 1px solid var(--btn-border); padding: var(--sp-xs) 0 var(--sp-xs) var(--sp-xl); margin: 0; }
+.quote q { font-size: 24px; line-height: 34px; letter-spacing: -.02em; color: var(--ink); display: block; }
+.quote cite { display: block; margin-top: var(--sp-md); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); font-style: normal; }
+
+.checks { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.check { background: var(--canvas-card); display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: var(--sp-md); padding: var(--sp-md) var(--sp-lg); align-items: baseline; }
+.check__i { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.check__t { font-size: 14px; line-height: 20px; color: var(--body); }
+
+.road { display: grid; gap: 1px; background: var(--hairline); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.road__r { background: var(--canvas-card); display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--sp-md); padding: var(--sp-md) var(--sp-lg); align-items: baseline; }
+.road__r:hover { background: var(--canvas-soft); }
+.road__i { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.road__t { font-size: 14px; line-height: 20px; color: var(--body); }
+
+/* ---------- 18. 复现:代码块(规范:Canvas Mid 用作代码底) ---------- */
+.code { background: var(--canvas-mid); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.code__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-md); padding: var(--sp-md) var(--sp-lg); border-bottom: 1px solid var(--hairline); background: var(--canvas-soft); }
+.code__t { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.copy {
+ font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px;
+ padding: var(--sp-xs) var(--sp-md);
+ background: transparent; border: 1px solid var(--btn-border); border-radius: var(--r-pill);
+ color: var(--ink);
+ transition: background var(--dur-fast) var(--ease-exp);
+}
+.copy:hover { background: var(--canvas-card); }
+.copy.is-done { border-color: var(--ink); }
+.code pre { margin: 0; padding: var(--sp-xl); overflow-x: auto; }
+.code code { font-family: var(--font-mono); font-size: 14px; line-height: 24px; color: var(--ink); white-space: pre; background: none; padding: 0; }
+.code code .k { color: var(--ink); }
+.code code .c { color: var(--mute); }
+
+/* ---------- 19. 实测样例 ---------- */
+.examples { display: grid; gap: var(--sp-xl); }
+.ex { background: var(--canvas-card); border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.ex__hd { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-sm); padding: var(--sp-md) var(--sp-xl); border-bottom: 1px solid var(--hairline); background: var(--canvas-soft); }
+.ex__no { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--ink); }
+.ex__q { margin: 0; padding: var(--sp-xl) var(--sp-xl) var(--sp-md); font-size: 24px; line-height: 32px; letter-spacing: -.02em; color: var(--ink); }
+.ex__exp { margin: 0; padding: 0 var(--sp-xl) var(--sp-xl); font-family: var(--font-mono); font-size: 12px; line-height: 20px; color: var(--mute); word-break: break-word; }
+.ex__exp span { color: var(--mute); margin-right: var(--sp-sm); }
+.ex__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border-top: 1px solid var(--hairline); }
+.ex__col { padding: var(--sp-xl); min-width: 0; }
+.ex__col + .ex__col { border-left: 1px solid var(--hairline); }
+.ex__col--nm { background: rgba(255, 255, 255, .015); }
+.ex__lab { margin: 0 0 var(--sp-md); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.ex__col--nm .ex__lab { color: var(--ink); }
+.ex__ans { margin: 0 0 var(--sp-md); font-size: 14px; line-height: 22px; color: var(--body); white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }
+.ex__meta { margin: 0; padding-top: var(--sp-md); border-top: 1px solid var(--hairline); font-family: var(--font-mono); font-size: 12px; line-height: 20px; letter-spacing: 1.2px; color: var(--mute); }
+.v--ok { color: var(--ink); }
+.v--no { color: var(--mute); }
+.ex__ev { margin-top: var(--sp-lg); padding-top: var(--sp-md); border-top: 1px solid var(--hairline); }
+.ex__evt { margin: 0 0 var(--sp-sm); font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.ex__evr { display: grid; gap: var(--sp-xs); padding: var(--sp-sm) 0; border-bottom: 1px solid var(--hairline); }
+.ex__evr:last-child { border-bottom: 0; padding-bottom: 0; }
+.ex__evk { font-family: var(--font-mono); font-size: 14px; color: var(--ink); word-break: break-word; }
+.ex__evv { font-size: 14px; line-height: 20px; color: var(--body); word-break: break-word; }
+.ex__evs { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); word-break: break-all; }
+
+/* ---------- 20. 上一节 / 下一节 ---------- */
+.pn { display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-lg); margin-top: var(--sp-4xl); padding-top: var(--sp-xl); border-top: 1px solid var(--hairline); }
+.pn__a {
+ display: grid; gap: var(--sp-sm); padding: var(--sp-lg) var(--sp-xl); position: relative;
+ background: var(--canvas-card); border: 1px solid var(--hairline); border-radius: var(--r-sm);
+ transition: background var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.pn__a:hover { background: var(--canvas-soft); border-color: var(--btn-border); }
+.pn__a--next { text-align: right; }
+.pn__k { font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.pn__t { font-size: 20px; line-height: 28px; color: var(--ink); }
+.pn__a .kbd { position: absolute; top: var(--sp-lg); right: var(--sp-lg); }
+.pn__a--prev .kbd { right: auto; left: var(--sp-lg); }
+.pn__x { display: block; }
+
+/* ---------- 21. 页脚(footer band: padding 48 24) ---------- */
+.footer { border-top: 1px solid var(--hairline); padding: var(--sp-3xl) 0; }
+.footer__in { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.05fr) minmax(0, 1.15fr); gap: var(--sp-3xl); align-items: start; }
+.footer__col--wide { min-width: 0; }
+.footer__h { margin: 0 0 var(--sp-md); font-family: var(--font-mono); font-size: 12px; line-height: 16px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.footer__note { margin: 0; font-size: 14px; line-height: 22px; color: var(--body); max-width: 64ch; }
+.footer__meta { margin: var(--sp-lg) 0 0; font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute); }
+.readout { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
+.readout > span {
+ display: inline-flex; align-items: center; padding: var(--sp-xs) var(--sp-md);
+ border: 1px solid var(--hairline); border-radius: var(--r-pill);
+ font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px; color: var(--mute);
+}
+
+/* ---------- 22. JS 渲染内容的淡入(只淡入,不位移 —— B2 的底线) ---------- */
+.reveal { opacity: 0; transition: opacity var(--dur-std) var(--ease-exp); }
+.reveal.is-in { opacity: 1; }
+
+/* 分带标题:眉标 + 标题的间距走令牌,不再用行内 style */
+.band__k { margin: 0 0 var(--sp-lg); }
+.band__k + .h2, .band__k + .h3 { margin-bottom: var(--sp-2xl); }
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; }
+ .reveal { opacity: 1; transform: none; transition: none; }
+ *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
+ .bar__fill, .vram__fill { transition: none; }
+ /* B8:关掉动效时直接呈现终态,不允许有任何内容因为「还没入场」而消失 */
+ .js .arr-wipe { clip-path: none !important; }
+ .js .arr-num, .js .arr-cards > *, .js .teaser .dgm > g:not(.dgm__a) {
+ opacity: 1 !important; transform: none !important;
+ }
+ .js .teaser .dgm__a path { stroke-dashoffset: 0 !important; }
+ .rst__b { opacity: 1 !important; }
+ .pal__box { animation: none; }
+ .hero__cue { animation: none; opacity: .6; }
+ #doc h2.is-flash, #doc h3.is-flash { animation: none; box-shadow: inset 3px 0 0 var(--ink); }
+ /* 交叉淡入淡出全部关掉后,页面仍然是完整的一帧 */
+}
+
+/* ---------- 23. 响应式(<768:hero 96→48,网格 1 列) ---------- */
+@media (min-width: 1120px) {
+ .g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
+ .g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
+}
+@media (max-width: 1080px) {
+ /* 顶栏在窄屏必须能收:给导航一个可横向滚动的弹性列,
+ 否则 7 个胶囊 + 检索按钮 + 状态胶囊会把整行撑出视口。 */
+ .topbar { gap: var(--sp-sm); }
+ .topbar__spacer { display: none; }
+ .topnav {
+ flex: 1 1 auto; min-width: 0;
+ flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
+ }
+ .topnav::-webkit-scrollbar { display: none; }
+ .brand__name { display: none; }
+ .shell { grid-template-columns: minmax(0, 1fr); gap: 0; }
+ .side {
+ position: static; max-height: none; overflow: visible;
+ border-right: 0; border-bottom: 1px solid var(--hairline);
+ padding: var(--sp-lg) 0;
+ }
+ .side__in { display: flex; gap: var(--sp-sm); overflow-x: auto; padding-right: 0; scrollbar-width: none; }
+ .side__in::-webkit-scrollbar { display: none; }
+ .side__grp { display: none; }
+ .side__list { display: flex; gap: var(--sp-sm); }
+ .side__a { grid-template-columns: auto auto; white-space: nowrap; padding: var(--sp-sm) var(--sp-md); border-left: 0; border: 1px solid var(--hairline); border-radius: var(--r-pill); }
+ .side__a.is-on { border-color: var(--ink); }
+ .g-2-1 { grid-template-columns: minmax(0, 1fr); }
+ .statrow { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .authorcard { grid-template-columns: 1fr; }
+ .footer__in { grid-template-columns: 1fr; }
+ .crumb { display: none; }
+ .flow { grid-template-columns: minmax(0, 1fr); }
+ .flow__detail { position: static; }
+}
+@media (max-width: 768px) {
+ .g2, .g3, .g4 { grid-template-columns: minmax(0, 1fr); }
+ .kpis { grid-template-columns: 1fr; }
+ .statrow { grid-template-columns: 1fr; }
+ .bar { grid-template-columns: 84px minmax(0, 1fr) 84px; gap: var(--sp-sm); }
+ .ledger__h { display: none; }
+ .ledger__r { grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-xs) var(--sp-md); }
+ .ledger__b { grid-column: 1 / -1; }
+ .ledger__d { text-align: left; }
+ .cap__r { grid-template-columns: minmax(0, 1fr); gap: var(--sp-xs); }
+ .cap__v { text-align: left; }
+ .state { grid-template-columns: minmax(0, 1fr); gap: var(--sp-xs); }
+ .gap { grid-template-columns: 1fr; }
+ .ex__grid { grid-template-columns: 1fr; }
+ .ex__col + .ex__col { border-left: 0; border-top: 1px solid var(--hairline); }
+ .pn { grid-template-columns: 1fr; }
+ .pn__a--next { text-align: left; }
+ .recjson__r { grid-template-columns: minmax(0, 1fr); gap: var(--sp-xxs); }
+ .tabs { width: 100%; }
+ .tab { flex: 1 1 auto; text-align: center; }
+ .doc { padding-top: var(--sp-3xl); }
+ .scrollx > .gt { min-width: 0; }
+ .gt--4 .gt__h, .gt--3 .gt__h { display: none; }
+ .gt--4 .gt__r, .gt--3 .gt__r { grid-template-columns: minmax(0, 1fr); gap: var(--sp-sm); padding: var(--sp-lg) var(--sp-md); }
+ .tbl-wrap, .scrollx {
+ background-image: linear-gradient(to left, rgba(255, 255, 255, .06), rgba(255, 255, 255, 0) 40px);
+ background-repeat: no-repeat; background-position: right center; background-size: 40px 100%;
+ }
+ :root { --band-tight: 40px; --band: 64px; --band-wide: 88px; }
+}
+
+/* ==================================================================
+ 24. 发布页版式(layout: release)
+ 按研究/模型发布页的通行骨架:发布头 → teaser → 关键数字 → 摘要 →
+ 发布清单 → 结果预览 → 深入阅读 → 边界 → 引用。
+ 规范约束:页面表面只用 canvas,band 之间用 divider-hairline 分隔,
+ 不引入第二种页面底色。
+ ================================================================== */
+.release { min-width: 0; }
+
+/* 段落留白不是常数(page-design.md §6):三档按语义给,
+ 紧的地方两块属于一组,宽的地方是留给观众的呼吸。 */
+.band { padding: var(--band) 0; }
+.band + .band { border-top: 1px solid var(--hairline); }
+.band--hero { padding-top: var(--band-tight); padding-bottom: var(--band-wide); }
+.band--tight { padding: var(--band-tight) 0; }
+.band--wide { padding: var(--band-wide) 0; }
+.band--teaser { padding: var(--band-tight) 0 var(--band); }
+.band--stats { padding: var(--band) 0; }
+.band--alt { /* 命名钩子:p-07 只有一种地面,不分带换底 */ }
+
+.wrap--narrow { max-width: 820px; }
+
+/* 顶栏快速导航(胶囊行) */
+.topnav { display: flex; flex-wrap: wrap; gap: var(--sp-sm); }
+.topnav__a {
+ padding: var(--sp-xs) var(--sp-md);
+ border: 1px solid transparent; border-radius: var(--r-pill);
+ color: var(--mute); font-size: 13px; line-height: 20px;
+ transition: color var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.topnav__a:hover { color: var(--ink); border-color: var(--hairline); }
+.topnav__a.is-on { border-color: var(--btn-border); color: var(--ink); }
+
+/* 侧栏返回发布页 */
+.side__back {
+ display: inline-flex; align-items: center;
+ margin: 0 0 var(--sp-xl) var(--sp-md);
+ padding: var(--sp-xs) var(--sp-md);
+ border: 1px solid var(--btn-border); border-radius: var(--r-pill);
+ font-size: 12px; line-height: 20px; letter-spacing: 1.2px; font-family: var(--font-mono);
+ color: var(--body);
+ transition: background var(--dur-fast) var(--ease-exp), color var(--dur-fast) var(--ease-exp);
+}
+.side__back:hover { background: var(--canvas-soft); color: var(--ink); }
+
+/* 发布头 */
+.release__badge { margin-bottom: var(--sp-xl); color: var(--mute); }
+.release__h1 { margin: 0 0 var(--sp-xl); max-width: 20ch; }
+.release__byline {
+ margin: 0 0 var(--sp-xl);
+ font-size: 14px; line-height: 20px; color: var(--body);
+ display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-sm);
+}
+.release__byline a { color: var(--ink); border-bottom: 1px solid var(--btn-border); }
+.release__byline a:hover { border-bottom-color: var(--ink); }
+.release__by { color: var(--mute); }
+.release__sep { color: var(--canvas-mid); }
+.release__lede { max-width: 68ch; margin: 0 0 var(--sp-2xl); }
+
+.linkrow { display: flex; flex-wrap: wrap; gap: var(--sp-md); align-items: center; }
+
+/* Teaser 全宽图 */
+.teaser {
+ margin: 0;
+ background: var(--canvas-card);
+ border: 1px solid var(--hairline); border-radius: var(--r-sm);
+ padding: var(--sp-xl);
+}
+.teaser .dgm { width: 100%; height: auto; }
+.teaser__cap {
+ margin-top: var(--sp-lg); padding-top: var(--sp-md);
+ border-top: 1px solid var(--hairline);
+ font-size: 14px; line-height: 22px; color: var(--mute); max-width: 92ch;
+}
+.teaser__k {
+ display: inline-block; margin-right: var(--sp-sm);
+ font-family: var(--font-mono); font-size: 12px; line-height: 16px;
+ letter-spacing: 1.2px; text-transform: uppercase; color: var(--ink);
+}
+
+/* 发布清单 */
+.rel__k { margin: 0 0 var(--sp-sm); font-family: var(--font-mono); font-size: 12px; line-height: 16px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--mute); }
+.rel__v { margin: 0 0 var(--sp-sm); font-family: var(--font-mono); font-size: 28px; line-height: 32px; letter-spacing: -.02em; color: var(--ink); }
+.rel__v small { font-size: 12px; letter-spacing: 1.2px; color: var(--mute); margin-left: var(--sp-xs); }
+.rel__d { margin: 0; font-size: 14px; line-height: 22px; color: var(--mute); }
+
+/* 正文内链接 */
+.linku { color: var(--ink); border-bottom: 1px solid var(--btn-border); }
+.linku:hover { border-bottom-color: var(--ink); }
+
+/* ==================================================================
+ 24. 抵达编排 —— 三种不同的抵达方式,不是全页统一 fade-up
+
+ design-slop B2:所有区块都 `opacity 0→1, translateY 20px` 会让页面变成
+ 一串相同的到达,读者到第三节就学会了无视它。所以这里只保留三处入场,
+ 而且**三处的手势互不相同**(擦除 / 数字冲入 / 侧向错峰),
+ 其余内容一律「本来就在那儿」。
+
+ 初始隐藏态全部写在 .js 下:脚本挂了内容不会被藏起来。
+ ================================================================== */
+
+/* 1 · 编辑式擦除:给「一句结论」用(choreography-arc 的 editorial 弧) */
+.js .arr-wipe {
+ clip-path: inset(0 0 106% 0);
+ transition: clip-path var(--dur-slow) cubic-bezier(.77, 0, .18, 1);
+}
+.arr-wipe.is-in { clip-path: inset(0 0 -2% 0); }
+
+/* 2 · 数字冲入:只给头号数字,带一点点压缩再回弹 */
+.js .arr-num {
+ opacity: 0; transform: translateY(16px) scale(.982);
+ transform-origin: 0 70%;
+ transition: opacity var(--dur-slow) var(--ease-exp), transform var(--dur-slow) var(--ease-sharp);
+}
+.arr-num.is-in { opacity: 1; transform: none; }
+
+/* 3 · 侧向错峰:只给一处列表,手势是从装订线推出来,不是往上浮 */
+.js .arr-cards > * {
+ opacity: 0; transform: translateX(-12px);
+ transition: opacity var(--dur-med) var(--ease-circ), transform var(--dur-med) var(--ease-circ);
+}
+.js .arr-cards.is-in > * { opacity: 1; transform: none; }
+.js .arr-cards.is-in > *:nth-child(1) { transition-delay: 0ms; }
+.js .arr-cards.is-in > *:nth-child(2) { transition-delay: 60ms; }
+.js .arr-cards.is-in > *:nth-child(3) { transition-delay: 120ms; }
+.js .arr-cards.is-in > *:nth-child(4) { transition-delay: 180ms; }
+.js .arr-cards.is-in > *:nth-child(5) { transition-delay: 240ms; }
+.js .arr-cards.is-in > *:nth-child(6) { transition-delay: 300ms; }
+
+/* 读取路径图:连接线自左向右绘出,环节随后依次点亮 —— 这一处是内容自己的
+ 语法(一条路径本来就是按顺序被走完的),所以保留,且它的手势与上面三种都不同 */
+.js .teaser .dgm__a path {
+ stroke-dasharray: 1; stroke-dashoffset: 1;
+ transition: stroke-dashoffset var(--dur-slow) var(--ease-exp);
+}
+.js .teaser.is-in .dgm__a path { stroke-dashoffset: 0; }
+.js .teaser.is-in .dgm__a path:nth-child(1) { transition-delay: 60ms; }
+.js .teaser.is-in .dgm__a path:nth-child(2) { transition-delay: 130ms; }
+.js .teaser.is-in .dgm__a path:nth-child(3) { transition-delay: 200ms; }
+.js .teaser.is-in .dgm__a path:nth-child(4) { transition-delay: 270ms; }
+.js .teaser.is-in .dgm__a path:nth-child(5) { transition-delay: 340ms; }
+.js .teaser.is-in .dgm__a path:nth-child(6) { transition-delay: 410ms; }
+.js .teaser.is-in .dgm__a path:nth-child(7) { transition-delay: 480ms; }
+.js .teaser .dgm > g:not(.dgm__a) { opacity: 0; transition: opacity var(--dur-med) var(--ease-exp); }
+.js .teaser.is-in .dgm > g:not(.dgm__a) { opacity: 1; }
+.js .teaser.is-in .dgm > g:nth-child(2) { transition-delay: 120ms; }
+.js .teaser.is-in .dgm > g:nth-child(3) { transition-delay: 200ms; }
+.js .teaser.is-in .dgm > g:nth-child(4) { transition-delay: 280ms; }
+.js .teaser.is-in .dgm > g:nth-child(5) { transition-delay: 360ms; }
+.js .teaser.is-in .dgm > g:nth-child(6) { transition-delay: 440ms; }
+.js .teaser.is-in .dgm > g:nth-child(7) { transition-delay: 520ms; }
+.js .teaser.is-in .dgm > g:nth-child(8) { transition-delay: 600ms; }
+.js .teaser.is-in .dgm > g:nth-child(9) { transition-delay: 680ms; }
+
+/* ==================================================================
+ 25. 重启实验装置
+ ================================================================== */
+.band--rst { padding-top: var(--sp-4xl); }
+.rst__lede { margin-bottom: var(--sp-2xl); }
+
+.rst {
+ border: 1px solid var(--hairline);
+ border-radius: var(--r-sm);
+ background: var(--canvas-card);
+ overflow: hidden;
+}
+
+/* --- 顶部控制条 --- */
+.rst__head {
+ display: flex; align-items: center; gap: var(--sp-lg);
+ padding: var(--sp-md) var(--sp-lg);
+ border-bottom: 1px solid var(--hairline);
+ flex-wrap: wrap;
+}
+.rst__file {
+ margin-left: auto; color: var(--mute); text-transform: none;
+ letter-spacing: 0; font-size: 11px;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
+}
+.seg {
+ display: inline-flex; padding: var(--sp-xxs);
+ border: 1px solid var(--hairline); border-radius: var(--r-pill);
+ background: var(--canvas);
+}
+.seg__b {
+ appearance: none; border: 0; cursor: pointer;
+ background: transparent; color: var(--mute);
+ font: inherit; font-size: 13px; letter-spacing: .01em;
+ padding: var(--sp-xs) var(--sp-lg); border-radius: var(--r-pill);
+ transition: color var(--dur-fast) var(--ease-exp), background-color var(--dur-fast) var(--ease-exp);
+}
+.seg__b:hover { color: var(--body); }
+.seg__b.is-on { background: var(--ink); color: var(--on-primary); }
+.seg__b.is-on[data-v="before"] { background: var(--canvas-mid); color: var(--ink); }
+.seg__b:focus-visible { outline: 2px solid var(--btn-border); outline-offset: 2px; }
+
+.rst__play { padding: var(--sp-xs) var(--sp-lg); font-size: 13px; }
+.rst__play::before {
+ content: ""; width: 0; height: 0; margin-right: var(--sp-sm);
+ border-left: 7px solid currentColor;
+ border-top: 5px solid transparent; border-bottom: 5px solid transparent;
+ display: inline-block; vertical-align: 1px;
+}
+.rst__play.is-playing::before { border-left-width: 6px; border-right: 6px solid currentColor; border-top-width: 6px; border-bottom-width: 6px; }
+
+/* --- 主体两栏 --- */
+.rst__grid {
+ display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr);
+ gap: 0;
+}
+
+/* --- 左栏:情境 + 状态 --- */
+.rst__aside {
+ padding: var(--sp-xl);
+ border-right: 1px solid var(--hairline);
+ background: var(--canvas);
+ min-width: 0;
+}
+.rst__k {
+ margin: 0 0 var(--sp-sm);
+ font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.4px;
+ text-transform: uppercase; color: var(--mute);
+}
+.rst__k + .rst__quote { margin-bottom: var(--sp-xl); }
+.rst__quote {
+ margin: 0; padding: 0 0 0 var(--sp-md);
+ border-left: 1px solid var(--btn-border);
+ font-size: 14px; line-height: 22px; color: var(--body);
+ text-wrap: pretty;
+}
+.rst__quote--q { color: var(--ink); }
+
+.rst__gauges { display: grid; gap: var(--sp-xl); }
+.rst__g { display: grid; gap: var(--sp-sm); }
+.rst__gt { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-md); }
+.rst__gt > span { font-size: 13px; color: var(--mute); }
+.rst__gt > b { font-size: 15px; letter-spacing: 1px; color: var(--ink); font-weight: 400; }
+.rst__gt > b small { font-size: 11px; color: var(--mute); letter-spacing: 1px; }
+.rst__track {
+ position: relative; height: 4px; border-radius: var(--r-pill);
+ background: var(--canvas-mid); overflow: hidden;
+}
+.rst__fill {
+ position: absolute; inset: 0 auto 0 0; width: 0;
+ border-radius: var(--r-pill); background: var(--mute);
+ transition: width var(--dur-slow) var(--ease-exp), background-color var(--dur-std) var(--ease-exp);
+}
+.rst__fill--store { background: var(--ink); }
+.rst__hint { margin: 0; font-family: var(--font-mono); font-size: 11px; letter-spacing: .6px; color: var(--mute); text-wrap: pretty; }
+
+/* --- 右栏:四个节拍 --- */
+.rst__beats { list-style: none; margin: 0; padding: 0; min-width: 0; }
+.rst__b {
+ position: relative;
+ display: grid; grid-template-columns: 36px minmax(0, 1fr);
+ gap: var(--sp-lg);
+ padding: var(--sp-xl);
+ border-bottom: 1px solid var(--hairline);
+ opacity: .28;
+ transition: opacity var(--dur-med) var(--ease-exp), background-color var(--dur-med) var(--ease-exp);
+}
+.rst__b:last-child { border-bottom: 0; }
+.rst__b.is-on { opacity: 1; background: var(--canvas-soft); }
+.rst__b.is-past { opacity: .62; }
+.rst__i {
+ font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.4px;
+ color: var(--mute); padding-top: 3px;
+}
+.rst__b.is-on .rst__i { color: var(--ink); }
+.rst__bt { margin: 0 0 var(--sp-sm); font-size: 16px; line-height: 22px; color: var(--ink); }
+.rst__bd { margin: 0; font-size: 13px; line-height: 21px; color: var(--mute); text-wrap: pretty; }
+/* 字段表始终参与布局:用 max-height 做展开动画在窄屏会截断较长的行,
+ 而且会造成跳动,所以层级只用节拍自身的透明度表达。 */
+.rst__data {
+ margin: var(--sp-md) 0 0; display: grid; gap: var(--sp-xs);
+ font-family: var(--font-mono); font-size: 12px; line-height: 19px;
+ letter-spacing: .4px; color: var(--body);
+}
+.rst__d { display: flex; gap: var(--sp-sm); }
+.rst__d > i { font-style: normal; color: var(--mute); min-width: 132px; flex: 0 0 auto; }
+.rst__d > span { min-width: 0; word-break: break-word; }
+
+/* --- 底部:模型输出 --- */
+.rst__out {
+ padding: var(--sp-xl);
+ border-top: 1px solid var(--hairline);
+ background: var(--canvas);
+ transition: opacity var(--dur-slow) var(--ease-exp);
+}
+.rst__out.is-idle { opacity: .38; }
+.rst__outhead {
+ display: flex; align-items: center; gap: var(--sp-md); margin-bottom: var(--sp-md);
+}
+.rst__outhead > .mono { color: var(--mute); }
+.rst__verdict { margin-left: auto; letter-spacing: 1.4px; }
+.rst__verdict.is-ok { color: var(--ink); }
+.rst__verdict.is-bad { color: var(--mute); }
+.rst__resp {
+ margin: 0; font-size: 22px; line-height: 32px; letter-spacing: -.01em;
+ color: var(--ink); text-wrap: pretty;
+ min-height: 32px;
+}
+/* 「答对了」这一帧不再用信号色:p-07 原型写明 one per page max,
+ 而信号色已经用在首屏画布里那颗目标记录上(accent 属于插图)。
+ 这里的含义由 ✓ 字形承担,跟「修复前 / 修复后」的对比一样,靠文字不靠颜色。 */
+.rst.is-payoff .rst__resp { color: var(--ink); }
+.rst.is-payoff .rst__verdict::before { content: ""; }
+.rst__kv {
+ display: flex; flex-wrap: wrap; gap: var(--sp-sm);
+ margin-top: var(--sp-lg); min-height: 26px;
+}
+.rst__kv > span {
+ display: inline-flex; align-items: baseline; gap: var(--sp-sm);
+ padding: var(--sp-xs) var(--sp-md);
+ border: 1px solid var(--hairline); border-radius: var(--r-pill);
+ font-family: var(--font-mono); font-size: 11px; letter-spacing: 1px;
+ color: var(--mute);
+}
+.rst__kv > span b { font-weight: 400; color: var(--body); }
+.rst__kv > span.is-hot { border-color: var(--btn-border); }
+.rst__kv > span.is-hot b { color: var(--ink); }
+
+.rst__note { margin-top: var(--sp-xl); max-width: 88ch; }
+
+@media (max-width: 1080px) {
+ .rst__grid { grid-template-columns: minmax(0, 1fr); }
+ .rst__aside { border-right: 0; border-bottom: 1px solid var(--hairline); }
+}
+@media (max-width: 768px) {
+ .rst__head { gap: var(--sp-md); }
+ /* 出处路径是证据本身,窄屏不截断,换行显示 */
+ .rst__file { margin-left: 0; width: 100%; white-space: normal; overflow: visible; text-overflow: clip; word-break: break-all; }
+ .rst__aside, .rst__b, .rst__out { padding: var(--sp-lg); }
+ .rst__b { grid-template-columns: 28px minmax(0, 1fr); gap: var(--sp-md); }
+ .rst__d { flex-direction: column; gap: 0; }
+ .rst__d > i { min-width: 0; }
+ .rst__resp { font-size: 18px; line-height: 26px; }
+}
+
+@media (max-width: 768px) {
+ .band { padding: var(--band-tight) 0; }
+ .release__h1 { max-width: none; }
+ .release__lede { margin-bottom: var(--sp-xl); }
+ .linkrow .btn { flex: 1 1 auto; justify-content: center; }
+ .teaser { padding: var(--sp-lg); }
+ .rel__v { font-size: 24px; line-height: 28px; }
+}
+
+/* ==================================================================
+ 29. 首屏:地址空间(本页的招牌机制)
+ 版面取舍来自 page-design §1/§3:
+ · hero **不是**「徽标 + 标题 + 副标题 + 两个按钮」的文字块,
+ 它就是这个机制本身,字被排进场景里(左上元信息 / 中间标题 / 右下读数)
+ · 不居中、没有按钮对、没有置顶徽标 —— A1 的四个特征一个都不占
+ 机制层见 assets/js/space.js;机制借用 press-stack 的「进度从文档读回来」,
+ 皮(配色 / 字体 / 结构)全部是本项目自己的。
+ ================================================================== */
+.hero { position: relative; height: 150vh; background: var(--canvas); }
+.hero__pin { position: sticky; top: 0; height: 100vh; overflow: hidden; }
+.hero__cv { position: absolute; inset: 0; display: block; }
+
+.hero__layer {
+ position: relative; z-index: 1;
+ height: 100%;
+ display: grid; grid-template-rows: auto 1fr auto;
+ padding: clamp(18px, 3.2vw, 44px) clamp(20px, 4vw, 56px) clamp(22px, 3.6vw, 48px);
+ pointer-events: none;
+}
+.hero__row { display: flex; min-width: 0; }
+.hero__row--top { justify-content: space-between; align-items: baseline; gap: var(--sp-xl); }
+.hero__row--mid { align-items: flex-end; justify-content: space-between; gap: clamp(24px, 5vw, 72px); }
+.hero__row--bot { justify-content: space-between; align-items: flex-end; gap: clamp(24px, 5vw, 72px); }
+
+.hero__geo { text-align: right; }
+.hero__h { max-width: 14ch; margin: 0; font-size: clamp(56px, 15vw, 132px); }
+
+/* 第一屏里的头号数字:读者不用滚三屏才知道这页在说什么 */
+.hero__fig { margin: 0; display: grid; gap: var(--sp-md); justify-items: end; text-align: right; max-width: 34ch; }
+.hero__fig-row { display: flex; align-items: baseline; gap: var(--sp-md); }
+.hero__fig-v {
+ font-size: clamp(38px, 4.4vw, 84px); line-height: .95;
+ letter-spacing: -.04em; font-weight: 700; font-stretch: 92%;
+ color: var(--ink); font-variant-numeric: tabular-nums;
+}
+.hero__fig-v small { font-size: .38em; font-weight: 500; letter-spacing: 0; margin-left: .04em; }
+.hero__fig-a { font-family: var(--font-mono); font-size: var(--fs-label); color: var(--mute); }
+.hero__fig-n {
+ font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: .8px;
+ line-height: 1.8; color: var(--mute);
+}
+.hero__lede-wrap { max-width: 44ch; min-width: 0; }
+.hero__lede { margin: 0 0 var(--sp-md); font-size: var(--fs-lede); line-height: 1.6; color: var(--body); }
+.hero__byline { margin: 0; color: var(--mute); }
+
+.hero__hud { display: grid; gap: var(--sp-xs); min-width: 250px; max-width: 34ch; text-align: right; }
+.hero__hud-i { color: var(--mute); }
+.hero__hud-n { font-size: 1rem; line-height: 1.4; color: var(--ink); }
+.hero__hud-v { color: var(--mute); }
+.hero__hud-track { display: block; height: 1px; margin-top: var(--sp-sm); background: var(--hairline); }
+.hero__hud-track > i { display: block; height: 100%; width: 0; background: var(--ink); }
+
+.hero__cue {
+ position: absolute; left: 50%; bottom: clamp(22px, 3.6vw, 48px);
+ transform: translateX(-50%);
+ margin: 0; color: var(--mute); white-space: nowrap;
+ animation: cue 2.6s var(--ease-exp) infinite;
+}
+@keyframes cue { 0%, 100% { opacity: .30; } 45% { opacity: .95; } }
+
+/* reduced-motion / 窄屏:不下降,就是一帧静态的地址空间 */
+.space--still .hero { height: auto; }
+.space--still .hero__pin { position: relative; height: 100vh; min-height: 560px; }
+.space--still .hero__cue { display: none; }
+
+@media (max-width: 900px) {
+ .hero { height: auto; }
+ .hero__pin { position: relative; height: auto; min-height: 92vh; padding-block: var(--sp-4xl); }
+ .hero__layer { grid-template-rows: auto auto auto; gap: var(--sp-3xl); }
+ .hero__row--mid { align-items: flex-start; flex-direction: column; }
+ .hero__fig { justify-items: start; text-align: left; max-width: none; }
+ .hero__fig-v { font-size: clamp(52px, 15vw, 84px); }
+ .hero__row--bot { flex-direction: column; align-items: flex-start; gap: var(--sp-xl); }
+ .hero__hud { text-align: left; min-width: 0; }
+ .hero__geo, .hero__cue { display: none; }
+}
+
+/* ==================================================================
+ 26. 全站检索(命令面板)
+ ================================================================== */
+.palbtn {
+ display: inline-flex; align-items: center; gap: var(--sp-sm);
+ height: 30px; padding: 0 var(--sp-md);
+ appearance: none; cursor: pointer; flex: none;
+ background: transparent; color: var(--mute);
+ border: 1px solid var(--hairline); border-radius: var(--r-pill);
+ font: inherit; font-size: 13px;
+ transition: color var(--dur-fast) var(--ease-exp), border-color var(--dur-fast) var(--ease-exp);
+}
+.palbtn:hover { color: var(--ink); border-color: var(--btn-border); }
+.palbtn:focus-visible { outline: 2px solid var(--btn-border); outline-offset: 2px; }
+.palbtn .kbd { min-width: 20px; height: 20px; font-size: 11px; }
+
+html.pal-open { overflow: hidden; }
+
+.pal { position: fixed; inset: 0; z-index: 80; }
+.pal__mask {
+ position: absolute; inset: 0;
+ background: rgba(0, 0, 0, .72);
+ backdrop-filter: blur(3px);
+}
+.pal__box {
+ position: relative;
+ width: min(720px, calc(100vw - 32px));
+ margin: 12vh auto 0;
+ background: var(--canvas-card);
+ border: 1px solid var(--hairline); border-radius: var(--r-sm);
+ overflow: hidden;
+ display: flex; flex-direction: column;
+ max-height: 70vh;
+ animation: palIn var(--dur-fast) var(--ease-exp);
+}
+@keyframes palIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
+
+.pal__hd {
+ display: flex; align-items: center; gap: var(--sp-md);
+ padding: var(--sp-md) var(--sp-lg);
+ border-bottom: 1px solid var(--hairline);
+}
+.pal__ico {
+ width: 12px; height: 12px; flex: none;
+ border: 1.5px solid var(--mute); border-radius: 50%;
+ position: relative;
+}
+.pal__ico::after {
+ content: ""; position: absolute; right: -5px; bottom: -4px;
+ width: 6px; height: 1.5px; background: var(--mute);
+ transform: rotate(45deg);
+}
+.pal__in {
+ flex: 1 1 auto; min-width: 0;
+ appearance: none; background: transparent; border: 0; outline: 0;
+ color: var(--ink); font: inherit; font-size: 16px; letter-spacing: -.005em;
+ padding: var(--sp-sm) 0;
+}
+.pal__in::placeholder { color: var(--mute); }
+
+.pal__list { overflow-y: auto; overscroll-behavior: contain; flex: 1 1 auto; }
+.pal__i {
+ display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
+ gap: var(--sp-lg); align-items: baseline;
+ padding: var(--sp-md) var(--sp-lg);
+ border-bottom: 1px solid var(--hairline);
+ color: inherit;
+}
+.pal__i:last-child { border-bottom: 0; }
+.pal__i.is-cur { background: var(--canvas-soft); }
+.pal__i-n { color: var(--mute); font-size: 11px; }
+.pal__i.is-cur .pal__i-n { color: var(--ink); }
+.pal__i-b { display: grid; gap: var(--sp-xs); min-width: 0; }
+.pal__i-h { font-size: 15px; line-height: 22px; color: var(--body); }
+.pal__i.is-cur .pal__i-h { color: var(--ink); }
+.pal__i-t {
+ font-size: 13px; line-height: 20px; color: var(--mute);
+ display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
+}
+.pal__i-g { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.2px; color: var(--mute); }
+.pal__i mark { background: transparent; color: var(--ink); box-shadow: inset 0 -1px 0 var(--ink); }
+.pal__none { margin: 0; padding: var(--sp-xl) var(--sp-lg); font-size: 14px; color: var(--mute); }
+.pal__ft {
+ display: flex; align-items: center; gap: var(--sp-lg);
+ padding: var(--sp-sm) var(--sp-lg);
+ border-top: 1px solid var(--hairline);
+ font-size: 12px; color: var(--mute);
+}
+.pal__ft .kbd { min-width: 20px; height: 20px; font-size: 11px; }
+.pal__n { margin-left: auto; }
+
+/* 检索跳转后闪一下目标标题 */
+#doc h2.is-flash, #doc h3.is-flash {
+ animation: flash 1700ms var(--ease);
+}
+@keyframes flash {
+ 0%, 100% { box-shadow: inset 3px 0 0 transparent; }
+ 12% { box-shadow: inset 3px 0 0 var(--ink); }
+ 70% { box-shadow: inset 3px 0 0 var(--ink); }
+}
+
+/* ==================================================================
+ 27. 剂量曲线(NM2.1 页)
+ ================================================================== */
+.dose {
+ margin: var(--sp-xl) 0 var(--sp-2xl);
+ padding: var(--sp-xl);
+ border: 1px solid var(--hairline); border-radius: var(--r-sm);
+ background: var(--canvas-card);
+}
+.dose__hd { display: flex; flex-wrap: wrap; gap: var(--sp-md); align-items: baseline; margin-bottom: var(--sp-lg); }
+.dose__hd .mono { color: var(--ink); text-transform: none; letter-spacing: .6px; font-size: 12px; }
+.dose__x, .dose__r {
+ display: grid; grid-template-columns: minmax(0, 190px) minmax(0, 1fr);
+ gap: var(--sp-lg); align-items: center;
+}
+.dose__x { margin-bottom: var(--sp-md); }
+.dose__x > span {
+ font-family: var(--font-mono); font-size: 12px; letter-spacing: 1.2px;
+ color: var(--mute); text-align: center;
+}
+.dose__x > span.is-pick { color: var(--ink); }
+.dose__r { padding: var(--sp-md) 0; border-top: 1px solid var(--hairline); }
+.dose__l { font-size: 14px; line-height: 20px; color: var(--body); display: grid; gap: var(--sp-xxs); }
+.dose__l small { font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.2px; color: var(--mute); }
+.dose__cells { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: var(--sp-xl); }
+.dose__c { display: grid; gap: var(--sp-sm); justify-items: center; }
+.dose__bar {
+ width: 100%; height: 6px; border-radius: var(--r-pill);
+ background: var(--canvas-mid); overflow: hidden;
+}
+.dose__fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--mute); }
+.dose__c.is-pick .dose__fill { background: var(--ink); }
+.dose__c b { font-family: var(--font-mono); font-size: 12px; font-weight: 400; letter-spacing: .6px; color: var(--mute); }
+.dose__c.is-pick b { color: var(--ink); }
+.dose__note { margin: var(--sp-lg) 0 0; color: var(--body); max-width: 88ch; }
+.dose__src { margin: var(--sp-sm) 0 0; color: var(--mute); }
+
+/* ==================================================================
+ 28. 真实评测台账
+ ================================================================== */
+.ledger-bar { display: flex; flex-wrap: wrap; gap: var(--sp-md); margin: var(--sp-2xl) 0 var(--sp-lg); }
+.ledger-bar .seg { flex-wrap: wrap; }
+.ledger-count {
+ display: flex; flex-wrap: wrap; gap: var(--sp-md); align-items: baseline;
+ padding-bottom: var(--sp-lg); border-bottom: 1px solid var(--hairline);
+}
+.ledger-count .mono { color: var(--ink); text-transform: none; letter-spacing: .6px; }
+.ledger { display: grid; }
+.lg { padding: var(--sp-xl) 0; border-bottom: 1px solid var(--hairline); }
+.lg:last-child { border-bottom: 0; }
+.lg__hd { display: flex; flex-wrap: wrap; gap: var(--sp-md); align-items: center; margin-bottom: var(--sp-md); }
+.lg__i { color: var(--mute); font-size: 11px; }
+.lg__id { margin-left: auto; }
+.lg__exp { margin: 0 0 var(--sp-sm); font-size: 14px; line-height: 22px; color: var(--mute); }
+.lg__exp > span { margin-right: var(--sp-md); font-family: var(--font-mono); font-size: 11px; letter-spacing: 1.2px; }
+.lg__exp code { color: var(--body); }
+.lg__rep {
+ margin: 0 0 var(--sp-md); font-size: 13px; line-height: 21px; color: var(--body);
+ padding-left: var(--sp-md); border-left: 1px solid var(--btn-border);
+ word-break: break-word;
+}
+.lg__meta { margin: 0; font-size: 11px; letter-spacing: .6px; color: var(--mute); }
+.lg__meta b { font-weight: 400; color: var(--body); }
+
+/* 发布页上「NM2.1」那一段的紧凑对照 */
+.nm21grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0; border: 1px solid var(--hairline); border-radius: var(--r-sm); overflow: hidden; }
+.nm21grid > div { padding: var(--sp-xl); border-right: 1px solid var(--hairline); }
+.nm21grid > div:last-child { border-right: 0; }
+.nm21grid .rel__v { font-size: 30px; line-height: 34px; }
+.nm21grid .rel__v em { font-style: normal; color: var(--mute); font-size: 20px; margin-right: var(--sp-sm); }
+
+/* 头号数字(money frame):整页唯一一个 .mega,放在 ~60% 滚动深度收网 */
+.money {
+ margin-top: var(--band-tight);
+ padding: var(--band-tight) 0;
+ border-top: 1px solid var(--hairline);
+ border-bottom: 1px solid var(--hairline);
+}
+.money__row {
+ display: flex; align-items: baseline; flex-wrap: wrap;
+ gap: clamp(14px, 3vw, 44px); margin: var(--sp-lg) 0 0;
+}
+.money .mega { color: var(--ink); }
+.money .mega small { font-size: .34em; letter-spacing: -.02em; margin-left: .03em; font-weight: 500; }
+.money__from {
+ font-family: var(--font-mono); font-size: var(--fs-label); letter-spacing: 1.2px;
+ color: var(--mute); max-width: 34ch; line-height: 1.8;
+}
+.money__cap { margin: var(--sp-xl) 0 0; max-width: var(--measure); font-size: var(--fs-small); line-height: 1.7; color: var(--body); }
+
+@media (max-width: 1080px) {
+ .dose__x, .dose__r { grid-template-columns: minmax(0, 1fr); gap: var(--sp-sm); }
+ .dose__x .dose__x0 { display: none; }
+ .dose__x > span { text-align: left; }
+ .nm21grid { grid-template-columns: minmax(0, 1fr); }
+ .nm21grid > div { border-right: 0; border-bottom: 1px solid var(--hairline); }
+ .nm21grid > div:last-child { border-bottom: 0; }
+}
+@media (max-width: 768px) {
+ .palbtn__t { display: none; }
+ .pal__box { margin-top: 6vh; max-height: 82vh; }
+ .pal__i { grid-template-columns: 26px minmax(0, 1fr); }
+ .pal__i-g { display: none; }
+ .dose, .lg { padding-left: 0; padding-right: 0; }
+ .dose { padding: var(--sp-lg); }
+}
diff --git a/assets/favicon.svg b/assets/favicon.svg
new file mode 100644
index 0000000..11ddc9e
--- /dev/null
+++ b/assets/favicon.svg
@@ -0,0 +1,15 @@
+
diff --git a/assets/fonts/archivo-latin-wdth-normal.woff2 b/assets/fonts/archivo-latin-wdth-normal.woff2
new file mode 100644
index 0000000..e2448d0
Binary files /dev/null and b/assets/fonts/archivo-latin-wdth-normal.woff2 differ
diff --git a/assets/fonts/geist-mono-latin-wght-normal.woff2 b/assets/fonts/geist-mono-latin-wght-normal.woff2
new file mode 100644
index 0000000..0750682
Binary files /dev/null and b/assets/fonts/geist-mono-latin-wght-normal.woff2 differ
diff --git a/assets/js/data.js b/assets/js/data.js
new file mode 100644
index 0000000..bdb6b6f
--- /dev/null
+++ b/assets/js/data.js
@@ -0,0 +1,334 @@
+/* ------------------------------------------------------------------
+ Natural Memory v2 — 站点数据层
+ 所有数字均直接摘自工程内的正式报告,src 字段标注出处文件。
+ 本文件不包含任何推算、外推或未测量的数字。
+ ------------------------------------------------------------------ */
+window.NM_DATA = {
+
+ meta: {
+ name: "Natural Memory v2",
+ version: "v2",
+ base: "Qwen3.5-4B",
+ date: "2026-09",
+ repo: "H:\\Memory",
+ disclaimer:
+ "本文只描述当前实现已经完成并实际测量的内容。它不把系统描述成通用智能,也不把实验室结果表述成线上服务承诺。"
+ },
+
+ /* ---------- 一句话定位 ---------- */
+ positioning: {
+ headline: "模型重启之后,它仍然记得。",
+ latin: "A memory layer inside the model.",
+ body:
+ "Natural Memory v2 是接在本地 Qwen3.5-4B 上的一个模型内记忆层:把适合长期复用的个人事实、项目事实和短对话片段写进带地址的记忆记录," +
+ "当前问题只读取少量相关记录,再把这些记录作为模型内部前缀交给 Qwen 生成答案。",
+ problem:
+ "要解决的具体问题:模型重启后不携带历史聊天记录,仍能访问已经保存的个人或项目事实,同时不把全部历史转换成 GPU 上的长 KV Cache。"
+ },
+
+ /* ---------- 记忆与 KV 的分工 ---------- */
+ division: [
+ { k: "最近对话", v: "Qwen 热 KV", note: "顺序关系仍由普通上下文负责" },
+ { k: "长期个人/项目事实", v: "Natural Memory 记录", note: "事实、版本、来源、短证据、语义地址" },
+ { k: "当前问题", v: "有界路由与 Top-K 读取", note: "不做全量 slot 注意力" },
+ { k: "原始持久化状态", v: "嵌入式 memory safetensors", note: "无 SQLite、无磁盘分页" }
+ ],
+
+ /* ---------- 读取路径(可交互的分步) ---------- */
+ readPath: [
+ { id: "q", label: "用户问题", detail: "普通自然语言,不要求 /remember 之类的显式指令。", io: "输入 · 一句口语化的问题" },
+ { id: "hs", label: "Qwen hidden state", detail: "由模型隐状态生成紧凑查询地址,而不是独立的外部 embedding 服务。", io: "问题 → 紧凑查询地址(单条 batch 编码,避免显存瞬时峰值)" },
+ { id: "lsh", label: "LSH / 地址粗索引", detail: "精确桶 + Hamming-1/2 探针,把候选收敛到少量页面。", io: "查询地址 → 候选页(批次 A 粗索引 730 个桶,最近一次粗筛返回 24 个候选)" },
+ { id: "pg", label: "候选页", detail: "页容量 32 条记录;默认最多读取 4 页。", io: "候选页 → 少量页面(本次 723 条 active records 分布在 23 个页面上)" },
+ { id: "rr", label: "页内记录重排", detail: "记录级重排由独立的 text_retriever 完成(6 张量、1,573,377 参数)。", io: "页内记录 → 有序候选(text_retriever 自身 Top-1 只有 23.20%,是当前主要瓶颈)" },
+ { id: "tk", label: "Top-K 记录", detail: "正式脏数据测试中的读取上限为 2 条。", io: "有序候选 → 少量证据(目标召回:批次 A 166/246,批次 B 228/246)" },
+ { id: "inj", label: "证据前缀注入 Qwen", detail: "命中的短证据被提升到 GPU,作为模型内部前缀。", io: "证据 → 模型内部前缀(批次 B 96.97% 的题实际注入,平均 410.34 token)" },
+ { id: "gen", label: "普通生成", detail: "记忆主体留在进程内存,只有热点记录进入有界 GPU cache。", io: "前缀 + 问题 → 回答(读取额外耗时:批次 A 61.10 ms → 批次 B 26.35 ms)" }
+ ],
+ readPathCost: { value: 61.1, unit: "ms", label: "读取路径额外耗时", src: "对外技术总结 §4" },
+
+ /* ---------- 记忆记录解剖 ---------- */
+ recordFields: [
+ { f: "raw_text", d: "原始短文本" },
+ { f: "semantic_addr", d: "语义地址" },
+ { f: "entity", d: "实体" },
+ { f: "attribute", d: "属性" },
+ { f: "value", d: "值" },
+ { f: "timestamp", d: "时间" },
+ { f: "source", d: "来源" },
+ { f: "confidence", d: "置信度" },
+ { f: "importance", d: "重要性" },
+ { f: "access_count", d: "访问次数" },
+ { f: "status", d: "状态" },
+ { f: "supersedes", d: "版本关系" },
+ { f: "related_ids", d: "关联记录" },
+ { f: "audit", d: "审计信息" }
+ ],
+ states: [
+ { s: "active", d: "当前有效事实,参与正常读取", tone: "ok" },
+ { s: "superseded", d: "同一实体+属性出现新值,旧值退役", tone: "mute" },
+ { s: "quarantined", d: "低置信度写入,需显式批准", tone: "warn" },
+ { s: "retracted", d: "已撤回,但不从审计中消失", tone: "bad" }
+ ],
+
+ /* ---------- 主结果:正式脏数据迁移测试 ---------- */
+ dirty: {
+ title: "正式脏数据迁移测试",
+ src: "对外技术总结 §3",
+ subtitle: "真实工程源码 / 文档 + 本项目对话中用户明确说过的短事实",
+ corpus: [
+ { k: "真实源码与文档", v: "65 个文件 · 1,110,524 字节 · 620 个分片" },
+ { k: "用户对话事实", v: "15 条记录" },
+ { k: "由记录派生的问题", v: "44 题(38 可回答 / 6 未知)" },
+ { k: "来自源码与文档", v: "220 题" },
+ { k: "题目总数", v: "264 题 · 246 可回答 · 18 未知" }
+ ],
+ protocol: "Qwen3.5-4B · 4-bit NF4 · greedy 解码 · 最大新生成 64 token · 进程显存上限 10 GiB · 读取上限 2 条记录 · 同一 tokenizer",
+ batchNote: "同一题集(264 题 / 246 可回答 / 18 未知)、同一协议、同一仓库,先后跑了两个独立批次。批次配置不同,因此两个批次的分数不可合并、不可平均,只能并列读。",
+ headline: [
+ { metric: "可回答正确率", base: 1.22, a: 62.60, b: 82.52, baseRaw: "3/246", aRaw: "154/246", bRaw: "203/246" },
+ { metric: "目标记忆召回率", base: 0.00, a: 67.48, b: 92.68, baseRaw: "0/246", aRaw: "166/246", bRaw: "228/246" },
+ { metric: "总体正确率", base: 7.58, a: 64.39, b: 82.95, baseRaw: "20/264", aRaw: "170/264", bRaw: "219/264" },
+ { metric: "未知拒答正确率", base: 94.44, a: 88.89, b: 88.89, baseRaw: "17/18", aRaw: "16/18", bRaw: "16/18" }
+ ],
+ batches: [
+ {
+ id: "A", label: "批次 A", date: "2026-09-05 23:22", file: "dirty_real_corpus_compare_4b.json",
+ tag: "对外技术总结采用",
+ reader: 61.10, promptTokens: 641.42, prefixRate: 90.91, prefixTokens: 610.67,
+ latencyBase: 2650.4, latencyNm: 2718.9, decodeBase: 24.06, decodeNm: 21.68,
+ allocNm: 3.134, resvNm: 5.021, records: 723, pages: 23, cacheTokens: 15885
+ },
+ {
+ id: "B", label: "批次 B", date: "2026-09-06 11:36", file: "dirty_real_corpus_compare_4b_router_final.json",
+ tag: "接入记忆路由器后",
+ reader: 26.35, promptTokens: 441.19, prefixRate: 96.97, prefixTokens: 410.34,
+ latencyBase: 2854.9, latencyNm: 2878.3, decodeBase: 22.34, decodeNm: 20.68,
+ allocNm: 3.134, resvNm: 5.180, records: 738, pages: 24, cacheTokens: 17990
+ }
+ ],
+ categories: [
+ { k: "symbol_navigation", label: "符号定位(函数在哪个文件)", n: 192, base: "0/192", a: "129/192", b: "181/192" },
+ { k: "unknown_refusal", label: "未知拒答", n: 18, base: "17/18", a: "16/18", b: "16/18" },
+ { k: "constraint", label: "约束类问题", n: 15, base: "0/15", a: "5/15", b: "6/15" },
+ { k: "paraphrase", label: "自然改写", n: 9, base: "0/9", a: "5/9", b: "5/9" },
+ { k: "cross_session", label: "跨会话", n: 6, base: "1/6", a: "5/6", b: "5/6" },
+ { k: "temporal_conflict", label: "时间冲突目录查询", n: 4, base: "0/4", a: "4/4", b: "4/4" },
+ { k: "reasoning", label: "推理", n: 4, base: "0/4", a: "2/4", b: "0/4" },
+ { k: "write_safety", label: "写入安全解释", n: 2, base: "0/2", a: "2/2", b: "0/2" },
+ { k: "baseline_limitation", label: "基线局限说明", n: 2, base: "2/2", a: "2/2", b: "2/2" },
+ { k: "operational_architecture", label: "操作性架构", n: 2, base: "0/2", a: "0/2", b: "0/2" },
+ { k: "architecture_tradeoff", label: "架构取舍", n: 2, base: "0/2", a: "0/2", b: "0/2" },
+ { k: "memory_tiers", label: "记忆分层", n: 2, base: "0/2", a: "0/2", b: "0/2" },
+ { k: "benchmark_protocol", label: "基准协议", n: 2, base: "0/2", a: "0/2", b: "0/2" },
+ { k: "control_path", label: "控制路径", n: 2, base: "0/2", a: "0/2", b: "0/2" },
+ { k: "router_path", label: "路由器路径", n: 2, base: "0/2", a: "0/2", b: "0/2" }
+ ],
+ categoryNote: "类别题量差异极大:符号定位 192 题,而 11 个类别各只有 2 题——2 题的类别里错 1 题就是 50 个百分点,不构成结论。批次 B 也并非全面提升:符号定位从 129 升到 181,但写入安全从 2/2 掉到 0/2、推理从 2/4 掉到 0/4。",
+ domains: [
+ { domain: "用户对话事实", n: 38, base: 2.63, baseRaw: "1/38", a: 55.26, aRaw: "21/38", b: 52.63, bRaw: "20/38" },
+ { domain: "真实源码/文档", n: 208, base: 0.96, baseRaw: "2/208", a: 63.94, aRaw: "133/208", b: 87.98, bRaw: "183/208" }
+ ],
+ gap: {
+ recallA: 67.48, recallARaw: "166/246",
+ answerA: 62.60, answerARaw: "154/246",
+ recallB: 92.68, recallBRaw: "228/246",
+ answerB: 82.52, answerBRaw: "203/246",
+ note: "目标记忆召回与最终回答正确必须分开报告——检索准确率不等于回答准确率。批次 A 的召回 67.48%、回答 62.60%(差 4.88pp);批次 B 召回 92.68%、回答 82.52%(差 10.16pp):路由器把该查的记忆查回来了,但证据到答案的闭环仍有丢失。"
+ }
+ },
+
+ /* ---------- 实测样例(逐字取自评测原始结果) ---------- */
+ examples: {
+ title: "实测样例",
+ src: "dirty_real_corpus_compare_4b_router_final.json(批次 B 逐条记录)",
+ subtitle: "下面五条题目与回答全部逐字取自评测原始结果,未经改写或润色。包含两条我们判错的样例。",
+ note:
+ "判分规则:可回答题要求 expected_anchors 里的锚点全部出现在回答中(去空白、转小写后做子串匹配);未知题则看模型是否拒答。" +
+ "输出上限为 64 token,标注「达上限截断」的回答是被硬截断的原文,不是完整句——我们保留原样,不做补全。" +
+ "另外:第 1 条命中的记忆记录里写的是「Natural Memory v1」,那是这条记录写入时的项目名,记录内容按原样保留、未做改写;" +
+ "第 5 条则是证据明明已经召回、回答却没能把锚点说全,因此判为未命中——这类「检索对、答案错」正是当前最主要的失分来源。"
+ },
+
+ /* ---------- Stage 5 结构化工程基准 ---------- */
+ stage5: {
+ title: "Stage 5 结构化工程基准",
+ src: "对外技术总结 §5",
+ subtitle: "与脏数据迁移集不是同一题集,不能合并成一个总分",
+ sets: [
+ {
+ name: "通用个人事实",
+ n: 107,
+ rows: [
+ { sys: "原版 Qwen3.5-4B", acc: 3.74, raw: "4/107", lat: 1233.2, tok: 23.51, abstain: "21/21" },
+ { sys: "强 RAG 基线", acc: 99.07, raw: "106/107", lat: 885.8, tok: 21.67, abstain: "20/21" },
+ { sys: "Natural Memory v2", acc: 100.00, raw: "107/107", lat: 994.3, tok: 21.70, abstain: "21/21" }
+ ]
+ },
+ {
+ name: "项目库",
+ n: 64,
+ rows: [
+ { sys: "原版 Qwen3.5-4B", acc: 70.31, raw: "45/64", lat: 1351.5, tok: 23.70, abstain: null },
+ { sys: "强 RAG 基线", acc: 100.00, raw: "64/64", lat: 1395.3, tok: 22.94, abstain: null },
+ { sys: "Natural Memory v2", acc: 100.00, raw: "64/64", lat: 1463.2, tok: 21.85, abstain: null }
+ ]
+ }
+ ],
+ note: "强 RAG 使用本地 CPU bert-base-chinese 向量、稀疏特征和固定规则重排。它不是训练好的公开 cross-encoder,因此可以作为更强的本地基线,但不能代表所有生产 RAG 服务。"
+ },
+
+ /* ---------- 代价账本 ---------- */
+ cost: {
+ title: "代价账本",
+ src: "对外技术总结 §4",
+ subtitle: "以下数字来自同一次正式脏数据测试。显存字段是每次请求结束后的 allocated/reserved 快照,不是 torch.cuda.max_memory_allocated() 记录的严格峰值。",
+ rows: [
+ { k: "平均总延迟", base: "2650.4 ms", nm2: "2718.9 ms", delta: "+2.58%", tone: "warn" },
+ { k: "平均解码速度", base: "24.06 tok/s", nm2: "21.68 tok/s", delta: "-9.90%", tone: "bad" },
+ { k: "平均输入 token", base: "30.8", nm2: "641.4", delta: "证据前缀增加", tone: "mute" },
+ { k: "读取额外耗时", base: "无", nm2: "61.1 ms", delta: "新增路径", tone: "mute" },
+ { k: "allocated 快照峰值", base: "3.084 GiB", nm2: "3.134 GiB", delta: "+0.050 GiB", tone: "ok" },
+ { k: "reserved 快照峰值", base: "3.234 GiB", nm2: "5.021 GiB", delta: "+1.787 GiB", tone: "warn" }
+ ],
+ vram: { card: 12, allocBase: 3.084, allocNm2: 3.134, resvBase: 3.234, resvNm2: 5.021, unit: "GiB" },
+ quote: "这个代价来自 Qwen 查询编码、稀疏路由、证据前缀和热点缓存,而不是免费获得的能力。"
+ },
+
+ /* ---------- 容量与存储 ---------- */
+ capacity: {
+ title: "容量与存储",
+ src: "对外技术总结 §4",
+ stats: [
+ { k: "active records", v: "723", d: "本次运行实际装载" },
+ { k: "pages", v: "23", d: "页容量 32 records/page" },
+ { k: "设计容量", v: "1,048,576", d: "地址空间容量,非已装入数量" },
+ { k: "GPU cache 上限", v: "256 records", d: "且 131,072 token" },
+ { k: "实际 GPU cache token", v: "15,885", d: "" },
+ { k: "GPU cache fallback", v: "0", d: "分配失败 0" },
+ { k: "cold page", v: "0", d: "本次为 embedded / process-RAM 模式" }
+ ],
+ storage: {
+ mode: "embedded weight-shard",
+ text: "记忆快照、路由器参数、页面元数据和短文本证据写入模型包的 memory safetensors 切片。启动时加载到进程内存,命中的热点记录才进入有界 GPU cache。",
+ tradeoff: "当前默认路径不使用 SQLite,也不使用磁盘分页。该设计减少了运行时依赖,但意味着模型包会随持久化记忆增长而变大,且需要重新保存权重切片才能固化更新。"
+ },
+ grid: { pagesTotal: 32768, perPage: 32, usedPages: 23 }
+ },
+
+ /* ---------- 记忆路由器工程线 ---------- */
+ router: {
+ title: "记忆路由器工程线",
+ src: "PRODUCTION_ROUTER.md · router_scorecard_final.md · V6_FINAL_REPORT.md · ZERO_OVERLAP_FINDINGS.md · WRITE_PATH_FIX.md",
+ subtitle: "128 维路由器替换原 512 维方案。以下为工程报告中的实测值。",
+ spec: [
+ { k: "参数量", v: "2,037,774" },
+ { k: "张量数", v: "16 (float32)" },
+ { k: "地址字节", v: "512 B" },
+ { k: "结构", v: "dim=128 · heads=8 · hidden=2560 · max_hops=3" },
+ { k: "权重摘要", v: "sha256 69f8295e…d189deb · 8,156,921 B" },
+ { k: "冻结评测集", v: "21,920 episode / 10 类" }
+ ],
+ metrics: [
+ { k: "Top-1 命中", base: "41.12%", nm2: "94.14%", note: "REPLAY-128" },
+ { k: "Recall@1 / @3 / @5", base: "37.03 / 46.73 / 48.91", nm2: "88.58 / 96.48 / 97.15", note: "" },
+ { k: "MRR / nDCG@3", base: "47.69 / 44.22", nm2: "95.77 / 95.37", note: "" },
+ { k: "多跳证据全中 (Top-3)", base: "43.43%", nm2: "96.22%", note: "" },
+ { k: "hop 正确率", base: "73.23%", nm2: "100.00%", note: "欠预测率 4.68% → 0.00%" },
+ { k: "未知拒答率", base: "0.00%", nm2: "100.00%", note: "门槛 0.30–0.80 每点均为 100%" },
+ { k: "单查询延迟", base: "1.0995 ms", nm2: "0.9663 ms", note: "路由 QPS 909.5 → 1034.8" },
+ { k: "batch256 吞吐", base: "207,287", nm2: "242,759", note: "texts/s 口径" },
+ { k: "零重叠改写 Top-1", base: "18.40%", nm2: "59.60%", note: "+41.20pp,随机基线 4.17%" },
+ { k: "写入路径存活记录", base: "12/20", nm2: "20/20", note: "retract 16 → 0;e2e 37.50% → 68.75%" },
+ { k: "逐轴判定", base: "—", nm2: "99 通过 / 11 未通过", note: "128 维两条线 22/22 全方位超越" }
+ ],
+ caveats: [
+ "端到端答案不由路由器决定:93.75% 的读取仍走旧版 16 槽注入路径,地址命中 0/16,记录级排序由 text_retriever 决定(其 Top-1 仅 23.20%)。路由器 +41.20pp 不传递到最终答案。",
+ "512 维方案「全部指标不劣于基线」在数学上不可达(地址存储 ×4 与 batch-256 吞吐两条独立障碍);XL 架构准确率最高,但吞吐真实低 26–52%。",
+ "拒答机制只在域内成立:跨域 A 段可回答 0.00%,集成门已回退为 gate=false;开放词表属性匹配仅 49.20% / AUC 0.6560。",
+ "属性头词表固定 24 类闭集:训练后新写入的属性会被判为 NONE 而误拒。",
+ "同一指标在多份报告中的延迟口径不一致(1.0995→0.9663、0.9549→0.9169 等),本页只列其中一套。",
+ "长上下文:8K 可跑,16K / 32K OOM。不得据此宣称百万级 token 上下文。"
+ ]
+ },
+
+ /* ---------- 边界:不能宣称的内容 ---------- */
+ limits: {
+ title: "我们不宣称的内容",
+ subtitle: "以下说法目前没有足够证据支持。把它们放在这里,是因为它们和上面的数字一样重要。",
+ claims: [
+ "Natural Memory 已经等价于百万 token 的完整 KV",
+ "Natural Memory 已经击败生产级 embedding + reranker RAG",
+ "任何自然语言表达都能稳定召回正确记忆",
+ "记忆写入永远不会出错",
+ "一百万条记录已经完成端到端验证",
+ "当前 4B 结果可以直接外推到 14B、32B 或更大模型",
+ "reserved VRAM 就是实际模型峰值显存",
+ "本次用户事实测试代表大规模真实用户群体"
+ ],
+ limitations: [
+ { t: "训练数据仍不够自然", d: "路由器主体训练仍来自本地构造的实体—属性—值事实和 hard negatives。自然改写仍是人工编写的测试变体,不是大规模脱敏真人会话。" },
+ { t: "真实仓库的语义索引覆盖有限", d: "620 个真实分片全部进库,但为控制 GPU 压力,只有 96 个符号目标和 8 个操作性事实建立了 Qwen 语义地址。这不等于整个仓库都被高质量索引。" },
+ { t: "开放式解释弱于明确定位", d: "「某个函数在哪个文件」明显好于「结合多个文件解释系统为什么这样设计」。后者需要多跳证据合并,仍会出现证据已召回但回答不完整。" },
+ { t: "速度仍有真实代价", d: "平均延迟高约 2.58%,解码速度低约 9.90%,输入前缀显著变长。" },
+ { t: "生产级基线还不完整", d: "本次对照聚焦原版 Qwen 无记忆基线;要作服务经济学结论,还需加入强 embedding 检索器、cross-encoder、滑动窗口和分页 KV。" }
+ ],
+ quotes: [
+ { q: "Memory Slot 不模拟完整 KV。它只接管 KV 中最昂贵、最适合长期保存、最容易重复利用的部分。", s: "README_NATURAL_MEMORY_V2.md" },
+ { q: "这些限制是设计边界,不是用一个「无限上下文」数字掩盖的未验证假设。", s: "README_NATURAL_MEMORY_V2.md" },
+ { q: "任何排序器都救不回一条已经不存在的记录。", s: "WRITE_PATH_FIX.md" },
+ { q: "这不是调参空间不够,而是这个信号根本不存在。", s: "ABSTENTION_BREAKTHROUGH.md" }
+ ]
+ },
+
+ /* ---------- 已解决的问题 ---------- */
+ solved: [
+ "普通自然语言触发自动记忆读取,不要求用户输入 /remember",
+ "模型重启后不回放历史聊天,只加载已保存的 memory shard",
+ "旧值和新值保留版本关系,旧值不会继续作为 active 事实参与正常读取",
+ "记忆读取器、地址索引和证据注入位于模型适配架构内部",
+ "记忆主体留在进程内存,热点记录使用有界 GPU cache",
+ "当前问题只读取 Top-K 记录,不对全量 slot 做注意力",
+ "低置信度写入、撤回和审计状态有独立表示",
+ "在真实工程源码定位任务上,相比无记忆 Qwen 有明显的可回答性提升"
+ ],
+
+ /* ---------- 路线图 ---------- */
+ roadmap: [
+ "使用有用户同意的脱敏对话,增加自然省略、指代、错别字、时间间隔、互相矛盾的旧说法和多轮项目协作样本",
+ "重新训练路由器,重点覆盖「同一事实的多种问法」和「相似但错误的记录」",
+ "对「检索正确但回答错误」的样本单独训练证据使用与拒答策略",
+ "将真实仓库的语义地址覆盖从目标记录扩展到完整索引,仍保持单条/小批次编码以保护显存",
+ "加入多跳证据合并和当前版本优先规则",
+ "将写入、冲突解决和摘要从生成热路径移到回合边界或后台线程",
+ "把显存测试改为记录真正的 CUDA high-water mark,并分别报告 live allocated、reserved 和 cache payload",
+ "在同一协议下补齐强 RAG、滑动窗口和分页 KV 的成本—质量曲线"
+ ],
+
+ /* ---------- 复现 ---------- */
+ reproduce: {
+ cwd: "H:\\Memory",
+ python: "C:\\Users\\Administrator\\miniconda3\\envs\\LLM\\python.exe",
+ cmd: [
+ "& C:\\Users\\Administrator\\miniconda3\\envs\\LLM\\python.exe `",
+ " -m dynamic_memory_lab.benchmark_dirty_real_corpus_4b `",
+ " --project-records 8192 `",
+ " --project-targets 96 `",
+ " --max-new-tokens 64 `",
+ " --encode-batch-size 1 `",
+ " --skip-chunk-rag `",
+ " --output H:\\Memory\\dynamic_memory_lab\\dirty_real_corpus_compare_4b.json"
+ ],
+ files: [
+ { k: "原始结果", v: "H:\\Memory\\dynamic_memory_lab\\dirty_real_corpus_compare_4b.json" },
+ { k: "评测脚本", v: "H:\\Memory\\dynamic_memory_lab\\benchmark_dirty_real_corpus_4b.py" },
+ { k: "对外技术说明", v: "H:\\Memory\\dynamic_memory_lab\\Natural_Memory_v2_对外技术总结.md" }
+ ]
+ },
+
+ /* ---------- 结论 ---------- */
+ conclusion:
+ "Natural Memory v2 当前已经不是只有构造数据上的演示:在真实工程源码、文档和本项目用户要求组成的脏数据迁移测试中," +
+ "它显著提高了窗口外事实的可回答性,并在 10 GiB GPU 进程上完成了 723 条 active records 的读取运行。" +
+ "同时,62.60% 的可回答正确率、88.89% 的未知拒答正确率和约 10% 的解码速度下降说明它仍处于研究原型到工程验证之间。"
+};
diff --git a/assets/js/examples.js b/assets/js/examples.js
new file mode 100644
index 0000000..b117a9f
--- /dev/null
+++ b/assets/js/examples.js
@@ -0,0 +1,226 @@
+/* 本文件由脚本从评测原始结果逐字提取,请勿手改。
+ 来源:H:\Memory\dynamic_memory_lab\dirty_real_corpus_compare_4b_router_final.json
+ 创建时间:2026-09-06T11:36:26+0800 协议:greedy / max_new_tokens=64 / top_k=2 */
+window.NM_EXAMPLES = [
+ {
+ "id": "user-004",
+ "domain": "user_dialogue",
+ "category": "paraphrase",
+ "answerable": true,
+ "query": "我不想翻聊天记录,当前这个记忆模型叫什么?",
+ "expected": "Natural Memory v1",
+ "expected_anchors": [
+ "Natural Memory v1"
+ ],
+ "nm": {
+ "response": "当前这个记忆模型叫 **Natural Memory v1**。",
+ "correct": true,
+ "tokens": 14,
+ "recall": true,
+ "retrieved_values": [
+ "Natural Memory v1",
+ "重要信息写入,禁止胡编乱造,并学习写入、清理和总结时机"
+ ],
+ "records": [
+ {
+ "record_id": "mem_da9418d179467fba",
+ "entity": "user",
+ "attribute": "模型名称",
+ "value": "Natural Memory v1",
+ "status": "active",
+ "version": 0,
+ "source": "user_provided_dialogue",
+ "text_preview": "用户给当前模型定名:Natural Memory v1。"
+ },
+ {
+ "record_id": "mem_862617d79b828161",
+ "entity": "user",
+ "attribute": "记忆质量要求",
+ "value": "重要信息写入,禁止胡编乱造,并学习写入、清理和总结时机",
+ "status": "active",
+ "version": 0,
+ "source": "user_provided_dialogue",
+ "text_preview": "用户要求:重要信息都要存进去,但不能让模型胡编乱造记忆,还要学会什么时候写入和清理总结。"
+ }
+ ]
+ },
+ "base": {
+ "response": "您提到的“当前这个记忆模型”具体指代什么,取决于您使用的平台或应用。如果您是在使用某个特定的 AI 助手或应用程序,那么该应用可能内置了某种形式的记忆功能,用于记住您的偏好、历史对话或其他相关信息。\n\n不过,从技术角度来看,并没有一个统一的“记忆",
+ "correct": false,
+ "tokens": 64
+ },
+ "note": "actual user-stated fact with a hand-written natural paraphrase"
+ },
+ {
+ "id": "code-symbol-010-a",
+ "domain": "real_repository",
+ "category": "symbol_navigation",
+ "answerable": true,
+ "query": "我现在不想翻完整个屎山代码库,_generate_student 这个 def 定义落在哪个文件?",
+ "expected": "benchmark_memory_vs_full_kv.py",
+ "expected_anchors": [
+ "benchmark_memory_vs_full_kv.py",
+ "_generate_student"
+ ],
+ "nm": {
+ "response": "根据已保存的长期记忆,`_generate_student` 这个函数定义落在文件 **`benchmark_memory_vs_full_kv.py`** 中(具体在第 171 行)。",
+ "correct": true,
+ "tokens": 43,
+ "recall": true,
+ "retrieved_values": [
+ "benchmark_memory_vs_full_kv.py",
+ "benchmark_compare_baselines_4b.py",
+ "test_restart_memory_dialogue.py"
+ ],
+ "records": [
+ {
+ "record_id": "mem_df66c88a7e2ea9da",
+ "entity": "benchmark_memory_vs_full_kv.py",
+ "attribute": "symbol:_generate_student",
+ "value": "benchmark_memory_vs_full_kv.py",
+ "status": "active",
+ "version": 0,
+ "source": "real_repository_symbol_index",
+ "text_preview": "真实代码库事实:文件 benchmark_memory_vs_full_kv.py 的第 171 行定义了 def _generate_student。原始代码行:def _generate_student("
+ },
+ {
+ "record_id": "mem_7fb124c563472b07",
+ "entity": "benchmark_compare_baselines_4b.py",
+ "attribute": "symbol:_base_generate",
+ "value": "benchmark_compare_baselines_4b.py",
+ "status": "active",
+ "version": 0,
+ "source": "real_repository_symbol_index",
+ "text_preview": "真实代码库事实:文件 benchmark_compare_baselines_4b.py 的第 116 行定义了 def _base_generate。原始代码行:def _base_generate("
+ }
+ ]
+ },
+ "base": {
+ "response": "我无法直接访问您的本地文件系统或代码库,因此无法告诉您 `_generate_student` 这个函数具体落在哪个文件中。\n\n不过,您可以尝试以下几种方法来快速定位:\n\n1. **使用 IDE 的搜索功能**:在您的代码编辑器(如 VS Code、PyCharm 等)中",
+ "correct": false,
+ "tokens": 64
+ },
+ "note": "real symbol extracted from repository source"
+ },
+ {
+ "id": "user-unknown-004",
+ "domain": "user_dialogue",
+ "category": "unknown_refusal",
+ "answerable": false,
+ "query": "我在这次对话里有没有说过生日?",
+ "expected": "",
+ "expected_anchors": [],
+ "nm": {
+ "response": "在这次对话中,**您还没有提到过生日**。\n\n截至目前,我们的交流内容中没有任何关于您生日、年龄或庆祝生日的提及。如果您想分享,随时可以告诉我!",
+ "correct": true,
+ "tokens": 41,
+ "recall": false,
+ "retrieved_values": [],
+ "records": []
+ },
+ "base": {
+ "response": "在这次对话中,**您还没有提到过生日**。\n\n截至目前,我们的交流内容中没有任何关于您生日、年龄或庆祝生日的提及。如果您想分享,随时可以告诉我!",
+ "correct": true,
+ "tokens": 41
+ },
+ "note": "unknown or deliberately sensitive field absent from the supplied dialogue slice"
+ },
+ {
+ "id": "code-unknown-008",
+ "domain": "real_repository",
+ "category": "unknown_refusal",
+ "answerable": false,
+ "query": "请告诉我没有出现在仓库里的部署区域。",
+ "expected": "",
+ "expected_anchors": [],
+ "nm": {
+ "response": "根据您提供的长期记忆证据(特别是 `README_NATURAL_MEMORY_V2.md` 的内容),**该文档中并没有列出任何具体的“部署区域”(Deployment Regions)**。\n\n文档主要关注的是以下技术架构和逻辑,而非地理或云端的部署位置:\n\n1. **核心设计",
+ "correct": false,
+ "tokens": 64,
+ "recall": false,
+ "retrieved_values": [
+ "README_NATURAL_MEMORY_V2.md",
+ "README_NATURAL_MEMORY_V2.md"
+ ],
+ "records": [
+ {
+ "record_id": "mem_796c6e8c86afce71",
+ "entity": "README_NATURAL_MEMORY_V2.md",
+ "attribute": "operational:1",
+ "value": "README_NATURAL_MEMORY_V2.md",
+ "status": "active",
+ "version": 0,
+ "source": "real_repository_snapshot",
+ "text_preview": "真实项目文件:README_NATURAL_MEMORY_V2.md\n## 核心设计\n\n### KV 与 Memory Slot 的分工\n\n```text\n最近 32K token(最多可配置到 128K) -> GPU 热 KV:保留精确顺序和局部连贯性\n长期个人事实 / 项目决策 / 纠错版本 -> Memory Slot:保存压缩、可寻址的证据\n当前问题 -> 稀疏路由:只读取少量相关记录\n```\n\nMemory Slot 不模拟完整 KV。它只接管 KV 中最昂贵、最适合长期保存、最容易重复利用的部分。这里的目标是比长 K"
+ },
+ {
+ "record_id": "mem_32331793e07a8a4f",
+ "entity": "README_NATURAL_MEMORY_V2.md",
+ "attribute": "operational:0",
+ "value": "README_NATURAL_MEMORY_V2.md",
+ "status": "active",
+ "version": 0,
+ "source": "real_repository_snapshot",
+ "text_preview": "真实项目文件:README_NATURAL_MEMORY_V2.md\n把一百万个 slot 当作一张巨大的 KV Cache,而是把历史中最适合长期保存的部分压缩成有地址的记忆记录;当前对话仍由 Qwen 的热 KV 负责连续理解。\n\n## 先看结论\n\n当前版本已经完成以下闭环:\n\n```text\n普通用户输入\n -> 自动写入策略\n -> 置信度 / 重要性 / 来源审计\n -> 紧凑地址投影\n -> 分页存储\n -> LSH 粗索引\n -> 候选页重排\n -> 候选记录精排\n -> Top-K 证据注入 Qwen\n -> 版本冲突 / "
+ }
+ ]
+ },
+ "base": {
+ "response": "您好!由于我无法访问您的仓库系统或具体业务数据,无法直接列出“未出现在仓库里的部署区域”。\n\n为了帮您准确定位,请提供以下信息:\n1. 仓库系统名称(如 GitLab、Jenkins、阿里云容器服务等)\n2. 您关注的部署区域范围",
+ "correct": true,
+ "tokens": 64
+ },
+ "note": "target deliberately absent from the real repository snapshot"
+ },
+ {
+ "id": "user-009",
+ "domain": "user_dialogue",
+ "category": "constraint",
+ "answerable": true,
+ "query": "你记得我对持久化文件和分页的限制吗?",
+ "expected": "第三个权重文件",
+ "expected_anchors": [
+ "第三个权重文件",
+ "不使用 SQLite",
+ "不使用磁盘分页"
+ ],
+ "nm": {
+ "response": "是的,我记住了。\n\n根据您之前的要求,关于持久化方案和分页的限制如下:\n* **持久化方案**:不使用 SQLite 或磁盘分页,所有信息完全由**第三个权重文件**储存。\n* **实体**:user\n* **属性**:记忆持久化",
+ "correct": false,
+ "tokens": 64,
+ "recall": true,
+ "retrieved_values": [
+ "第三个权重文件,不使用 SQLite 或磁盘分页",
+ "Qwen3.5 4B"
+ ],
+ "records": [
+ {
+ "record_id": "mem_c3886345cac35147",
+ "entity": "user",
+ "attribute": "记忆持久化方案",
+ "value": "第三个权重文件,不使用 SQLite 或磁盘分页",
+ "status": "active",
+ "version": 0,
+ "source": "user_provided_dialogue",
+ "text_preview": "用户要求:不使用 SQLite 或磁盘分页,完全由第三个权重文件储存所有信息。"
+ },
+ {
+ "record_id": "mem_149a0c2ab04c0f56",
+ "entity": "user",
+ "attribute": "对照模型",
+ "value": "Qwen3.5 4B",
+ "status": "active",
+ "version": 0,
+ "source": "user_provided_dialogue",
+ "text_preview": "用户要求和原版 qwen3.5 4b 做跑分对比,并纠正为 4B。"
+ }
+ ]
+ },
+ "base": {
+ "response": "作为一个人工智能模型,我**无法访问您的个人历史对话记录、系统配置或您之前设定的具体规则**(例如具体的持久化文件大小限制、分页数量限制等)。每次对话对我来说都是全新的开始,除非您在当前的对话中明确告知我这些限制。\n\n不过,我可以帮您**重新设定",
+ "correct": false,
+ "tokens": 64
+ },
+ "note": "actual user-stated fact with a hand-written natural paraphrase"
+ }
+];
diff --git a/assets/js/ledger.js b/assets/js/ledger.js
new file mode 100644
index 0000000..87282b0
--- /dev/null
+++ b/assets/js/ledger.js
@@ -0,0 +1,11 @@
+/* ==================================================================
+ Natural Memory v2 — ledger.js
+ 「真实评测台账」的数据层。
+
+ ⚠️ 本文件由 tools/make_ledger.py 从磁盘上的原始评测 JSON 生成,
+ 请勿手改。下面是全部 108 条 episode,字段逐字复制、未做改写或换算。
+
+ runtime_score_bands.json
+ nm2_1_final_runtime_e2e.json
+ ================================================================== */
+window.NM_LEDGER = [{"key":"bands","label":"运行时评分带扫描","note":"60 用例 · 24 同形候选 · prior=1.00 blend=0.00","n":60,"items":[{"id":"zero-overlap-000047","attr":"咖啡口味","ans":true,"exp":"VAL-PW46K9K9","reply":"关于您早上饮用的咖啡偏好哪种豆子,当前记忆中没有相关记录。已知信息仅包含您的午餐偏好是 VAL-BFW4LVTV。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000144","attr":"入职年份","ans":false,"exp":"VAL-GLHQMRN6","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;档案标识;VAL-AVX95V7L;手机尾号;VAL-LBRPQJRT","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":2.8524921381220096,"active":23,"need":true},{"id":"zero-overlap-000259","attr":"出生城市","ans":true,"exp":"VAL-M9VSJAEX","reply":"您的出生城市是 VAL-M9VSJAEX。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.564332926884773,"active":24,"need":true},{"id":"zero-overlap-000143","attr":"档案标识","ans":true,"exp":"VAL-NPXYS6SC","reply":"您的紧急联系人姓氏是 VAL-DDN4WAD4,客户名称是 VAL-7UJT3QQA,课程名称是 VAL-V3TE4HHW,团队名称是 VAL-TLE8GCW3,阅读","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":4.211679219488425,"active":24,"need":true},{"id":"zero-overlap-000266","attr":"出生城市","ans":true,"exp":"VAL-K7FT5XFB","reply":"您的出生城市是 VAL-K7FT5XFB。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.3051346188937454,"active":24,"need":true},{"id":"zero-overlap-000204","attr":"阅读工具","ans":true,"exp":"VAL-ZRLSPU5V","reply":"您的阅读工具是 VAL-ZRLSPU5V。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.9886870104974497,"active":24,"need":true},{"id":"zero-overlap-000061","attr":"常住城市","ans":true,"exp":"VAL-3SUNPNRD","reply":"您的常住城市是 VAL-3SUNPNRD,通勤方式是 VAL-2CXTE3TB,出生城市是 VAL-GE828D22,办公城市是 VAL-XYRY8BUQ,入职年份","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.6213454890251158,"active":24,"need":true},{"id":"zero-overlap-000091","attr":"运动习惯","ans":true,"exp":"VAL-92MQHEDL","reply":"您的运动习惯是 VAL-92MQHEDL。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.0058205354213714,"active":24,"need":true},{"id":"zero-overlap-000193","attr":"午餐偏好","ans":true,"exp":"VAL-HG84H5TD","reply":"您的午餐偏好是 user;午餐偏好;VAL-HG84H5TD。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.6998433045297862,"active":24,"need":true},{"id":"zero-overlap-000088","attr":"设备型号","ans":true,"exp":"VAL-JY6S7EYF","reply":"关于您使用的机器档次,当前长期记忆中未包含此信息,无法回答。已知信息仅涉及:user;常用编辑器;VAL-3KPKRM4J。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.516791512356487,"active":24,"need":true},{"id":"zero-overlap-000121","attr":"手机尾号","ans":true,"exp":"VAL-LHY34JYF","reply":"您随身那台设备的末端数字是 XX2。设备型号:VAL-JFSYSXX2。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000282","attr":"咖啡口味","ans":true,"exp":"VAL-Q2AQQMUP","reply":"关于您早上那杯咖啡的豆子偏好,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-5ATEHVSM。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000261","attr":"课程名称","ans":false,"exp":"VAL-Q6ZMN3XU","reply":"关于您的上级信息,当前长期记忆中未包含相关记录。已知信息包括:紧急联系人姓氏是 VAL-QT3YM55W;客户名称是 VAL-7VDS2BRU;默认语言是 VAL-QB","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":1.9421720507257656,"active":23,"need":true},{"id":"zero-overlap-000104","attr":"午餐偏好","ans":true,"exp":"VAL-SRFC842D","reply":"您的午餐偏好是 VAL-SRFC842D。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7423390823602677,"active":24,"need":true},{"id":"zero-overlap-000214","attr":"客户名称","ans":true,"exp":"VAL-MV382EEF","reply":"我服务的对象是 VAL-MV382EEF。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7693164377659559,"active":24,"need":true},{"id":"zero-overlap-000175","attr":"客户名称","ans":true,"exp":"VAL-QJNRRE8X","reply":"您的客户名称是 VAL-QJNRRE8X。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7712558816373348,"active":24,"need":true},{"id":"zero-overlap-000078","attr":"常住城市","ans":true,"exp":"VAL-TQBY65U8","reply":"我的常住城市是 VAL-TQBY65U8。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.2650369691848753,"active":24,"need":true},{"id":"zero-overlap-000281","attr":"咖啡口味","ans":true,"exp":"VAL-3GP4STZX","reply":"关于您早上饮用的咖啡偏好哪种豆子,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-XNN7T8EK。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000285","attr":"起床时间","ans":true,"exp":"VAL-4K3RQQKF","reply":"我的闹钟设定时间是 VAL-4K3RQQKF。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.359343914853775,"active":24,"need":true},{"id":"zero-overlap-000092","attr":"宿舍楼号","ans":true,"exp":"VAL-PLZZ659B","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-PLZZ659B。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.8819549524530643,"active":24,"need":true},{"id":"zero-overlap-000000","attr":"出生城市","ans":true,"exp":"VAL-PHB8FNF9","reply":"您的出生城市是 VAL-PHB8FNF9。user;出生城市;VAL-PHB8FNF9","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.8505235558902053,"active":24,"need":true},{"id":"zero-overlap-000080","attr":"入职年份","ans":true,"exp":"VAL-GFT4HRX2","reply":"关于您何时开始在这家公司上班,当前记忆中没有相关记录。已知信息:user;办公城市;VAL-TCNMN9HW。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.51429933919514,"active":24,"need":true},{"id":"zero-overlap-000198","attr":"手机尾号","ans":true,"exp":"VAL-7T3XVMVH","reply":"您随身那台设备的末端数字是 4N,该设备的完整型号为 VAL-9MWPDS4N。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000140","attr":"宿舍楼号","ans":true,"exp":"VAL-RMDSB297","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-RMDSB297。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.942847951625275,"active":24,"need":true},{"id":"zero-overlap-000006","attr":"档案标识","ans":true,"exp":"VAL-8EFN3KGR","reply":"您的系统分配字符包括:紧急联系人姓氏 VAL-KLPQW2L2;课程名称 VAL-PVMQLCL9;团队名称 VAL-V4BVFB2N;客户名称 VAL-92LH7CHG","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":4.169416844133658,"active":24,"need":true},{"id":"zero-overlap-000180","attr":"客户名称","ans":false,"exp":"VAL-3F87H6UT","reply":"您随身那台设备的末端数字是 VAL-X5VZ48QF。","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":23,"need":true},{"id":"zero-overlap-000258","attr":"阅读工具","ans":true,"exp":"VAL-SHRY3GNG","reply":"我翻书时依赖的阅读工具是 VAL-SHRY3GNG。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.9932317454522837,"active":24,"need":true},{"id":"zero-overlap-000030","attr":"课程名称","ans":true,"exp":"VAL-BQY3K5EV","reply":"您的课程名称是 VAL-BQY3K5EV。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7088197509944439,"active":24,"need":true},{"id":"zero-overlap-000196","attr":"手机尾号","ans":true,"exp":"VAL-ZHN2NMZV","reply":"您随身那台设备的末端数字是 W9,该设备的完整型号为 VAL-5VBJ22W9。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000246","attr":"常住城市","ans":false,"exp":"VAL-6BWJYDE4","reply":"关于您小时候待的地方,当前长期记忆中未包含此信息。已知信息如下:user;常住城市;VAL-6BWJYDE4;办公城市;VAL-88V99MPM;宿舍楼号","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":2.6527658945475845,"active":23,"need":true},{"id":"zero-overlap-000268","attr":"宿舍楼号","ans":true,"exp":"VAL-QVYMWTLK","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-QVYMWTLK。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.7659836388747943,"active":24,"need":true},{"id":"zero-overlap-000219","attr":"咖啡口味","ans":true,"exp":"VAL-M3W7MRJR","reply":"关于您早上那杯咖啡的豆子偏好,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-Y4Z5H2FG。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000056","attr":"主管姓名","ans":true,"exp":"VAL-VDL8H7Y9","reply":"您的主管姓名是 user;主管姓名;VAL-VDL8H7Y9。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.9513123025616852,"active":24,"need":true},{"id":"zero-overlap-000195","attr":"午餐偏好","ans":false,"exp":"VAL-LHKPHTRT","reply":"关于您坐在哪一间房的信息,当前长期记忆中未包含该数据。已知信息包括:user;宿舍楼号;VAL-JCMMXZA3。","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":3.194271833706213,"active":23,"need":true},{"id":"zero-overlap-000183","attr":"出生城市","ans":true,"exp":"VAL-TT5RG83H","reply":"您的出生城市是 VAL-TT5RG83H。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.549031222478035,"active":24,"need":true},{"id":"zero-overlap-000168","attr":"常住城市","ans":true,"exp":"VAL-8TQ8737C","reply":"您的常住城市是 VAL-8TQ8737C。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.4585979866981504,"active":24,"need":true},{"id":"zero-overlap-000184","attr":"项目代号","ans":true,"exp":"VAL-3H5TQEC4","reply":"您的团队名称是 VAL-Z98JC7BG。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":3.191310502301591,"active":24,"need":true},{"id":"zero-overlap-000051","attr":"项目代号","ans":false,"exp":"VAL-KG4RZYH5","reply":"您的办公城市是 VAL-W9KGRD9U。","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":1.2606107878684996,"active":23,"need":true},{"id":"zero-overlap-000164","attr":"客户名称","ans":true,"exp":"VAL-BB55YHW5","reply":"您的客户名称是 VAL-BB55YHW5。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.77552460052073,"active":24,"need":true},{"id":"zero-overlap-000213","attr":"通勤方式","ans":true,"exp":"VAL-M58DDQX2","reply":"您的通勤方式是 VAL-M58DDQX2。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.1366783114608343,"active":24,"need":true},{"id":"zero-overlap-000122","attr":"通勤方式","ans":true,"exp":"VAL-VULSNGJ3","reply":"您的通勤方式是 VAL-VULSNGJ3。user;通勤方式;VAL-VULSNGJ3","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.130069571965557,"active":24,"need":true},{"id":"zero-overlap-000106","attr":"咖啡口味","ans":false,"exp":"VAL-837DYYWV","reply":"关于您的闹钟设置时间,当前长期记忆中未包含相关证据,无法回答。已知信息包括:user;闹钟设置时间;未知。","ok":false,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.273330240117752,"active":23,"need":true},{"id":"zero-overlap-000215","attr":"档案标识","ans":true,"exp":"VAL-B44BGU2X","reply":"您的系统分配字符为:user;紧急联系人姓氏;VAL-SEUHFJR6;阅读工具;VAL-4WUUDSWA;团队名称;VAL-4LFCHQ5W;课程名称;VAL","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":4.2204755537550875,"active":24,"need":true},{"id":"zero-overlap-000173","attr":"客户名称","ans":false,"exp":"VAL-P84HTKZR","reply":"您随身那台设备的末端数字是 Y。\n设备型号:VAL-NARXVSSY","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":23,"need":true},{"id":"zero-overlap-000155","attr":"主管姓名","ans":true,"exp":"VAL-NTC877NR","reply":"您的主管姓名是 VAL-NTC877NR。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.9677336550748197,"active":24,"need":true},{"id":"zero-overlap-000045","attr":"办公城市","ans":false,"exp":"VAL-CG4X6ZWD","reply":"您的办公城市是 VAL-SZ4R5GT3。","ok":false,"wrong":false,"leak":true,"stop":"evidence_found","sel":8,"score":1.1886139500141142,"active":23,"need":true},{"id":"zero-overlap-000002","attr":"午餐偏好","ans":true,"exp":"VAL-HEE2QMH2","reply":"您的午餐偏好是 user;午餐偏好;VAL-HEE2QMH2。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7196446585655213,"active":24,"need":true},{"id":"zero-overlap-000171","attr":"常住城市","ans":true,"exp":"VAL-KZA246K9","reply":"您的常住城市是 VAL-KZA246K9。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.286561994552612,"active":24,"need":true},{"id":"zero-overlap-000154","attr":"入职年份","ans":true,"exp":"VAL-7Z9CBUX8","reply":"关于您何时开始在这家公司上班,当前记忆中没有相关记录。已知信息:user;办公城市;VAL-BP7253WL。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.51429933919514,"active":24,"need":true},{"id":"zero-overlap-000105","attr":"客户名称","ans":true,"exp":"VAL-SNB45547","reply":"我服务的对象是 VAL-SNB45547。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7797372966259718,"active":24,"need":true},{"id":"zero-overlap-000021","attr":"默认语言","ans":true,"exp":"VAL-DUQ9DJ8G","reply":"您的交流语言是 VAL-9QAURGAS。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":1.9399472272499265,"active":24,"need":true},{"id":"zero-overlap-000009","attr":"咖啡口味","ans":true,"exp":"VAL-7PQL6BVT","reply":"关于您早上饮用的咖啡偏好哪种豆子,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-WS8VG4B5。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000234","attr":"工位楼层","ans":true,"exp":"VAL-87M7HVC7","reply":"关于您坐在哪一间房的信息,当前长期记忆中未包含该数据。已知信息如下:user;宿舍楼号;VAL-JTSHA7PM。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":3.1937497879965546,"active":24,"need":true},{"id":"zero-overlap-000244","attr":"午餐偏好","ans":false,"exp":"VAL-2VLHC26V","reply":"关于您服务的对象,当前长期记忆中未包含相关信息,无法回答。","ok":false,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.7779876451194286,"active":23,"need":true},{"id":"zero-overlap-000083","attr":"办公城市","ans":true,"exp":"VAL-YBVYV97X","reply":"您的办公城市是 VAL-YBVYV97X。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.8081361769552853,"active":24,"need":true},{"id":"zero-overlap-000079","attr":"出生城市","ans":true,"exp":"VAL-7R8WVAFG","reply":"您的出生城市是 VAL-7R8WVAFG。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.7214163786326675,"active":24,"need":true},{"id":"zero-overlap-000235","attr":"项目代号","ans":true,"exp":"VAL-DCA9FZL4","reply":"您的团队名称是 VAL-QZLU73KN。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":3.1913125220273892,"active":24,"need":true},{"id":"zero-overlap-000025","attr":"紧急联系人姓氏","ans":true,"exp":"VAL-STECQ3PX","reply":"根据长期记忆证据,当出现问题时,应联系主管,其姓名代码为 VAL-ZS6KE6E2。可直接复述的关键短语:user;主管姓名;VAL-ZS6KE6E2。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":8.516015553315961,"active":24,"need":true},{"id":"zero-overlap-000187","attr":"手机尾号","ans":true,"exp":"VAL-PXRHJ49F","reply":"您随身那台设备的末端数字是 H9,该设备的完整型号为 VAL-ZPUPV9H9。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000005","attr":"项目代号","ans":true,"exp":"VAL-BLPA66RK","reply":"您的项目代号是 VAL-BLPA66RK。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.191348113781957,"active":24,"need":true}]},{"key":"e2e","label":"NM2.1 最终运行时","note":"48 用例 · 24 同形候选 · prior=1.00 blend=0.50","n":48,"items":[{"id":"zero-overlap-000259","attr":"出生城市","ans":true,"exp":"VAL-M9VSJAEX","reply":"您的出生城市是 VAL-M9VSJAEX。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.313681895152214,"active":24,"need":true},{"id":"zero-overlap-000219","attr":"咖啡口味","ans":true,"exp":"VAL-M3W7MRJR","reply":"关于您早上那杯咖啡的豆子偏好,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-Y4Z5H2FG。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000193","attr":"午餐偏好","ans":true,"exp":"VAL-HG84H5TD","reply":"您的午餐偏好是 user;午餐偏好;VAL-HG84H5TD。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.953831069469452,"active":24,"need":true},{"id":"zero-overlap-000080","attr":"入职年份","ans":true,"exp":"VAL-GFT4HRX2","reply":"关于您何时开始在这家公司上班,当前记忆中没有相关记录。已知信息:user;办公城市;VAL-TCNMN9HW。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.51429933919514,"active":24,"need":true},{"id":"zero-overlap-000285","attr":"起床时间","ans":true,"exp":"VAL-4K3RQQKF","reply":"我的闹钟设定时间是 VAL-4K3RQQKF。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.2757300050727958,"active":24,"need":true},{"id":"zero-overlap-000246","attr":"常住城市","ans":false,"exp":"VAL-6BWJYDE4","reply":"作为一个人工智能助手,我并没有“小时候”的概念,也无法知道您具体待过哪里。\n\n不过,如果您是想询问**您自己**小时候待的地方,或者想聊聊关于某个特定地点的童年回忆,我很乐意","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000204","attr":"阅读工具","ans":true,"exp":"VAL-ZRLSPU5V","reply":"我翻书时依赖的长期记忆中没有关于“翻书”习惯的记录,因此无法回答您依赖什么。","ok":false,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.989592991362775,"active":24,"need":true},{"id":"zero-overlap-000078","attr":"常住城市","ans":true,"exp":"VAL-TQBY65U8","reply":"您的常住城市是 VAL-TQBY65U8。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.4848385143280027,"active":24,"need":true},{"id":"zero-overlap-000104","attr":"午餐偏好","ans":true,"exp":"VAL-SRFC842D","reply":"您的午餐偏好是 user;午餐偏好;VAL-SRFC842D。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.1867529082298276,"active":24,"need":true},{"id":"zero-overlap-000281","attr":"咖啡口味","ans":true,"exp":"VAL-3GP4STZX","reply":"关于您早上饮用的咖啡偏好哪种豆子,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-XNN7T8EK。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000106","attr":"咖啡口味","ans":false,"exp":"VAL-837DYYWV","reply":"作为一个人工智能助手,我**无法直接访问您的设备、查看您的闹钟设置或获取您的实时时间信息**。\n\n要找到您的闹钟设定时间,您可以尝试以下方法:\n\n1. **查看手机/电脑屏幕","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000091","attr":"运动习惯","ans":true,"exp":"VAL-92MQHEDL","reply":"您的运动习惯是 VAL-92MQHEDL。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.8434395718574524,"active":24,"need":true},{"id":"zero-overlap-000088","attr":"设备型号","ans":true,"exp":"VAL-JY6S7EYF","reply":"关于您使用的机器档次,当前长期记忆中未包含此信息,无法回答。已知信息仅涉及:user;常用编辑器;VAL-3KPKRM4J。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.516791512356487,"active":24,"need":true},{"id":"zero-overlap-000244","attr":"午餐偏好","ans":false,"exp":"VAL-2VLHC26V","reply":"您好!作为一个人工智能助手,我并没有直接服务于特定某一家公司或机构。我的设计目标是**为所有用户**提供通用、安全、有帮助的信息和服务。\n\n不过,如果您是在某个具体平台(如企业微信","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000282","attr":"咖啡口味","ans":true,"exp":"VAL-Q2AQQMUP","reply":"关于您早上那杯咖啡的豆子偏好,当前记忆中没有相关记录。已知信息仅包含:user;午餐偏好;VAL-5ATEHVSM。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000056","attr":"主管姓名","ans":true,"exp":"VAL-VDL8H7Y9","reply":"您的主管姓名是 VAL-VDL8H7Y9。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.049178622612938,"active":24,"need":true},{"id":"zero-overlap-000184","attr":"项目代号","ans":true,"exp":"VAL-3H5TQEC4","reply":"您的项目代号是 VAL-3H5TQEC4。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.4711107313126073,"active":24,"need":true},{"id":"zero-overlap-000198","attr":"手机尾号","ans":true,"exp":"VAL-7T3XVMVH","reply":"您随身那台设备的末端数字是 4N,该设备的完整型号为 VAL-9MWPDS4N。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000258","attr":"阅读工具","ans":true,"exp":"VAL-SHRY3GNG","reply":"我翻书时依赖的实体是 user,属性是阅读工具,已确认值为 VAL-SHRY3GNG。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.9794869642364628,"active":24,"need":true},{"id":"zero-overlap-000030","attr":"课程名称","ans":true,"exp":"VAL-BQY3K5EV","reply":"您的课程名称是 VAL-BQY3K5EV。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.1944098997116086,"active":24,"need":true},{"id":"zero-overlap-000196","attr":"手机尾号","ans":true,"exp":"VAL-ZHN2NMZV","reply":"您随身那台设备的末端数字是 W9,该设备的完整型号为 VAL-5VBJ22W9。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000268","attr":"宿舍楼号","ans":true,"exp":"VAL-QVYMWTLK","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-QVYMWTLK;常住城市;VAL-NW2E2ZWE;","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.7256882035478824,"active":24,"need":true},{"id":"zero-overlap-000006","attr":"档案标识","ans":true,"exp":"VAL-8EFN3KGR","reply":"您的档案标识是 VAL-8EFN3KGR。user;档案标识;VAL-8EFN3KGR","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.7116483740847537,"active":24,"need":true},{"id":"zero-overlap-000047","attr":"咖啡口味","ans":true,"exp":"VAL-PW46K9K9","reply":"关于您早上饮用的咖啡偏好哪种豆子,当前记忆中没有相关记录。已知信息仅包含您的午餐偏好是 VAL-BFW4LVTV。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.515033109300843,"active":24,"need":true},{"id":"zero-overlap-000173","attr":"客户名称","ans":false,"exp":"VAL-P84HTKZR","reply":"作为一个人工智能助手,我**无法知道您随身设备的型号或数字**,因为我没有访问您个人设备信息的权限,也无法看到您正在使用的具体设备。\n\n如果您指的是:\n- **手机/电脑型号**:","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000000","attr":"出生城市","ans":true,"exp":"VAL-PHB8FNF9","reply":"您的出生城市是 VAL-PHB8FNF9。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.455323672186914,"active":24,"need":true},{"id":"zero-overlap-000266","attr":"出生城市","ans":true,"exp":"VAL-K7FT5XFB","reply":"您的出生城市是 VAL-K7FT5XFB。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.1730912274276046,"active":24,"need":true},{"id":"zero-overlap-000051","attr":"项目代号","ans":false,"exp":"VAL-KG4RZYH5","reply":"作为一个人工智能助手,我并没有实体,也没有“待得最久”的物理空间或地理位置。我的“存在”体现在每一次与您的对话中。\n\n不过,如果您是在寻找**您自己**待得最久的地方","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000061","attr":"常住城市","ans":true,"exp":"VAL-3SUNPNRD","reply":"您的常住城市是 VAL-3SUNPNRD,办公城市是 VAL-XYRY8BUQ,出生城市是 VAL-GE828D22,通勤方式是 VAL-2CXTE3TB,入职年份","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.6610547590255735,"active":24,"need":true},{"id":"zero-overlap-000214","attr":"客户名称","ans":true,"exp":"VAL-MV382EEF","reply":"您的服务对象是 VAL-MV382EEF。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.2588214325904845,"active":24,"need":true},{"id":"zero-overlap-000092","attr":"宿舍楼号","ans":true,"exp":"VAL-PLZZ659B","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-PLZZ659B。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.809071942542481,"active":24,"need":true},{"id":"zero-overlap-000140","attr":"宿舍楼号","ans":true,"exp":"VAL-RMDSB297","reply":"关于您睡觉的地儿是第几排,当前长期记忆中未包含此信息。已知信息如下:user;宿舍楼号;VAL-RMDSB297。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.8702512824281925,"active":24,"need":true},{"id":"zero-overlap-000164","attr":"客户名称","ans":true,"exp":"VAL-BB55YHW5","reply":"您的客户名称是 VAL-BB55YHW5。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.2650055932998656,"active":24,"need":true},{"id":"zero-overlap-000213","attr":"通勤方式","ans":true,"exp":"VAL-M58DDQX2","reply":"您的通勤方式是 VAL-M58DDQX2。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.0363681795533712,"active":24,"need":true},{"id":"zero-overlap-000122","attr":"通勤方式","ans":true,"exp":"VAL-VULSNGJ3","reply":"您的通勤方式是 VAL-VULSNGJ3。user;通勤方式;VAL-VULSNGJ3","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.0330638098057325,"active":24,"need":true},{"id":"zero-overlap-000168","attr":"常住城市","ans":true,"exp":"VAL-8TQ8737C","reply":"您的常住城市是 VAL-8TQ8737C。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.4606248426437376,"active":24,"need":true},{"id":"zero-overlap-000175","attr":"客户名称","ans":true,"exp":"VAL-QJNRRE8X","reply":"您的客户名称是 VAL-QJNRRE8X。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.2628431367874144,"active":24,"need":true},{"id":"zero-overlap-000144","attr":"入职年份","ans":false,"exp":"VAL-GLHQMRN6","reply":"作为一个人工智能助手,我**无法知道您睡觉的具体位置**,因为我无法感知现实世界中的物理环境,也无法获取您的个人生活信息。\n\n如果您是在询问某个特定场所(如学校教室、电影院、酒店或","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000002","attr":"午餐偏好","ans":true,"exp":"VAL-HEE2QMH2","reply":"您的午餐偏好是 user;午餐偏好;VAL-HEE2QMH2。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":0.9400680589675904,"active":24,"need":true},{"id":"zero-overlap-000171","attr":"常住城市","ans":true,"exp":"VAL-KZA246K9","reply":"您的常住城市是 VAL-KZA246K9,办公城市是 VAL-Z8QBE5HQ,出生城市是 VAL-N8SWTE6N,宿舍楼号是 VAL-XZY4WPYH,通勤","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":1.49963095664978,"active":24,"need":true},{"id":"zero-overlap-000154","attr":"入职年份","ans":true,"exp":"VAL-7Z9CBUX8","reply":"关于您何时开始在这家公司上班,当前记忆中没有相关记录。已知信息:user;办公城市;VAL-BP7253WL。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.51429933919514,"active":24,"need":true},{"id":"zero-overlap-000234","attr":"工位楼层","ans":true,"exp":"VAL-87M7HVC7","reply":"您的工位楼层是 VAL-87M7HVC7。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":3.1934859675651266,"active":24,"need":true},{"id":"zero-overlap-000083","attr":"办公城市","ans":true,"exp":"VAL-YBVYV97X","reply":"您的办公城市是 VAL-YBVYV97X。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.3717270611639645,"active":24,"need":true},{"id":"zero-overlap-000079","attr":"出生城市","ans":true,"exp":"VAL-7R8WVAFG","reply":"您的出生城市是 VAL-7R8WVAFG。","ok":true,"wrong":false,"leak":false,"stop":"evidence_found","sel":8,"score":2.4271069115554122,"active":24,"need":true},{"id":"zero-overlap-000180","attr":"客户名称","ans":false,"exp":"VAL-3F87H6UT","reply":"作为一个人工智能助手,我**无法知道您随身设备的型号或数字**,因为我没有访问您个人设备信息的权限,也无法看到您正在使用的具体设备。\n\n如果您指的是:\n- **手机/电脑型号**:","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false},{"id":"zero-overlap-000025","attr":"紧急联系人姓氏","ans":true,"exp":"VAL-STECQ3PX","reply":"您的默认语言是 VAL-PRVTUCP7,课程名称是 VAL-93ME7BJ5,起床时间是 VAL-PJC3D3W9,出生城市是 VAL-KYBRBBKV,咖啡口味是","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":8,"score":8.516015553315961,"active":24,"need":true},{"id":"zero-overlap-000187","attr":"手机尾号","ans":true,"exp":"VAL-PXRHJ49F","reply":"您随身那台设备的末端数字是 H9,该设备的完整型号为 VAL-ZPUPV9H9。","ok":false,"wrong":true,"leak":false,"stop":"evidence_found","sel":1,"score":8.513663491707547,"active":24,"need":true},{"id":"zero-overlap-000045","attr":"办公城市","ans":false,"exp":"VAL-CG4X6ZWD","reply":"作为一个人工智能助手,我并没有实体,也没有“待得最久”的物理空间或地理位置。我的“存在”体现在每一次与您的对话中。\n\n不过,如果您是在寻找**您自己**待得最久的地方","ok":false,"wrong":false,"leak":false,"stop":"attribute_not_covered","sel":0,"score":0.0,"active":23,"need":false}]}];
diff --git a/assets/js/nm21.js b/assets/js/nm21.js
new file mode 100644
index 0000000..e4fc5d6
--- /dev/null
+++ b/assets/js/nm21.js
@@ -0,0 +1,50 @@
+/* ==================================================================
+ Natural Memory v2 — nm21.js
+ 「v2.1 之后做了什么」页面上需要交互的那部分数据。
+
+ 只放剂量曲线:它们是这一轮唯一需要「拖动/悬停看数值」的东西,
+ 其余表格都直接写在正文里并标注了来源文件,便于逐条核对。
+
+ ⚠️ 数值逐字取自工程内报告,未做改写或换算:
+ V2_dpskw\NM2_1_FINAL.md 第 3 节(两处改动 + 剂量曲线)
+ V2_dpskw\record_blend_dose_response.md / prior_scale_dose_response.md 原始产物
+ ================================================================== */
+window.NM21 = {
+ /* 改动一:记录排序混合权重(独立进程测量,48 用例,先验保持 1.0)
+ 此前的同进程测量因顺序污染已作废,这里用的是独立进程那一版。 */
+ blend: {
+ title: "记录排序混合权重 · 剂量曲线",
+ axis: "memory_record_router_blend",
+ xLabel: "blend 值(0 = 仅 retriever,1 = 仅路由器)",
+ pick: 0.5,
+ metrics: [
+ { key: "answer", label: "可回答正确率", unit: "%", higherBetter: true,
+ v: [65.00, 70.00, 62.50] },
+ { key: "wrong", label: "答成别的属性", unit: "%", higherBetter: false,
+ v: [35.00, 27.50, 37.50] },
+ { key: "leak", label: "未知泄漏率", unit: "%", higherBetter: false,
+ v: [75.00, 75.00, 87.50] }
+ ],
+ x: [0, 0.5, 1],
+ note: "两个打分器互补:0.50 是可回答正确率最高、答成别的属性最低的点。" +
+ "blend=1.00 反而全面变差,说明这不是单调关系 —— 单看某一端会得出错的结论。"
+ },
+
+ /* 改动二:把加法先验暴露成参数后测出来「不该动」。
+ 这是一个有价值的负面结论:它否定了「先验压过学习打分」这条假设。 */
+ prior: {
+ title: "记录打分先验权重 · 剂量曲线",
+ axis: "加法先验的缩放系数",
+ xLabel: "先验缩放(原值为 1.00)",
+ pick: 1.0,
+ metrics: [
+ { key: "answer", label: "可回答正确率", unit: "%", higherBetter: true,
+ v: [65.00, 47.50, 42.50, 40.00] }
+ ],
+ x: [1, 0.5, 0.25, 0],
+ note: "调小先验只会更差(65.00 → 47.50 → 42.50 → 40.00),所以保持 1.00 不动。" +
+ "这条曲线的作用是排除一个假设,而不是带来增益。"
+ },
+
+ srcLabel: "V2_dpskw\\NM2_1_FINAL.md §3 · 原始产物 prior1_blend_{0.0,0.5,1.0}_proc.json 与 prior_scale_dose_response.json"
+};
diff --git a/assets/js/restart.js b/assets/js/restart.js
new file mode 100644
index 0000000..c0fe87e
--- /dev/null
+++ b/assets/js/restart.js
@@ -0,0 +1,115 @@
+/* ==================================================================
+ Natural Memory v2 — restart.js
+ 「重启实验」互动装置的数据层。
+
+ ⚠️ 本文件的每一个数值都逐字取自工程内的原始 JSON,未做任何改写、
+ 换算或估算。两个来源文件:
+ A. dynamic_memory_lab\natural_memory_v2_restart_test_v7.json (修复前)
+ B. dynamic_memory_lab\natural_memory_v2_restart_test_v7_final.json (修复后)
+ 两次运行使用同一条事实、同一个问题、同一份协议,唯一差别是
+ 「写入是否真的落库」(write_changed: false → true)。
+
+ 修改此处等于修改一个「证据」页面,请先核对原始 JSON 再改。
+ ================================================================== */
+window.NM_RESTART = {
+ eyebrow: "Evidence · 重启实验",
+ h2: "把历史扔掉,它还记得吗?",
+ lede: "下面不是示意动画,是工程里两次真实运行的逐字回放。同一条事实、同一个问题、同一份协议;" +
+ "两次之间唯一的差别,是写入有没有真的落库。",
+
+ fact: "我正在开发一个长期项目,项目内部代号是NM-V2-RESTART,使用中文。",
+ query: "我正在开发的长期项目内部代号是什么?只回答代号。",
+ expected: "NM-V2-RESTART",
+ historyPassedToRestart: false,
+
+ /* 读取路径的四个节拍(两次运行共用同一套节拍名) */
+ beats: [
+ { i: "01", t: "写入", s: "automatic_write: true —— 事实作为一条带地址的记录进入记忆库" },
+ { i: "02", t: "重启", s: "history_passed_to_restart: false —— 重启不携带任何对话历史" },
+ { i: "03", t: "召回", s: "路由器在当前问题与有界候选之间定位证据记录" },
+ { i: "04", t: "作答", s: "命中的短证据作为模型内部前缀注入 Qwen,再由普通生成产出答案" }
+ ],
+
+ variants: [
+ {
+ key: "before",
+ label: "修复前",
+ tone: "bad",
+ file: "natural_memory_v2_restart_test_v7.json",
+ /* --- 写入 --- */
+ writeChanged: false,
+ /* --- 重启后模型看到的内部状态 --- */
+ prefixLength: 0,
+ prefixUsed: false,
+ /* --- 路由决策 --- */
+ needMemory: false,
+ pageIds: [],
+ recordIds: [],
+ pageScores: [],
+ recordScores: [],
+ hopCount: 1,
+ confidence: 0.5,
+ stopReason: "below_read_threshold",
+ /* --- 生成结果 --- */
+ response: "Project Phoenix",
+ recalled: false,
+ correct: false,
+ /* --- 存储快照(before_save) --- */
+ store: {
+ records: 0, active_records: 0, pages: 0, resident_records: 0,
+ coarse_index_buckets: 0, gpu_cache_records: 0, gpu_cache_tokens: 0,
+ gpu_cache_bytes: 0
+ }
+ },
+ {
+ key: "after",
+ label: "修复后",
+ tone: "ok",
+ file: "natural_memory_v2_restart_test_v7_final.json",
+ writeChanged: true,
+ prefixLength: 45,
+ prefixUsed: true,
+ needMemory: true,
+ pageIds: ["page_00000001"],
+ recordIds: ["mem_3f5823eb77810b49"],
+ pageScores: [0.48235471503517796],
+ recordScores: [0.5923714575763567],
+ hopCount: 1,
+ confidence: 0.7961857287881784,
+ stopReason: "evidence_found",
+ response: "NM-V2-RESTART",
+ recalled: true,
+ correct: true,
+ store: {
+ records: 1, active_records: 1, pages: 1, resident_records: 1,
+ coarse_index_buckets: 1, gpu_cache_records: 1, gpu_cache_tokens: 45,
+ gpu_cache_bytes: 1429
+ }
+ }
+ ],
+
+ /* 常量:出自同一份 JSON 的存储与预算字段(两次运行相同) */
+ limits: {
+ maxPages: 32768,
+ pageCapacity: 32,
+ capacityRecords: 1048576,
+ kvBudgetTokens: 32768,
+ kvTriggerTokens: 29491,
+ gpuCacheLimitRecords: 256,
+ gpuCacheLimitTokens: 131072,
+ gpuCacheReserveMB: 2048,
+ gpuCacheDevice: "cuda:0"
+ },
+
+ /* 同一个实验的另外两次运行,用于说明「不是每次都成功」 */
+ otherRuns: [
+ { file: "natural_memory_v2_restart_test.json", date: "2026-09-04 23:16",
+ writeChanged: true, prefixLength: 36, recalled: true, quarantined: 2 },
+ { file: "restart_memory_natural_v7.json", date: "2026-09-04 09:15",
+ note: "另一条事实(工作地点代号 R7)。复位 token 后 memory_norm 归零,模型随即答不出——" +
+ "这条记录的是「清掉记忆会怎样」的对照组。" }
+ ],
+
+ note: "两次运行的判定字段(router_recalled_after_restart / generated_contains_expected)分别为 " +
+ "false / false 与 true / true。我们没有为发布页润色过任何一条模型输出,包括修复前那次编造出来的答案。"
+};
diff --git a/assets/js/search.js b/assets/js/search.js
new file mode 100644
index 0000000..38f6283
--- /dev/null
+++ b/assets/js/search.js
@@ -0,0 +1,2 @@
+/* 由 build.py 生成:顶栏命令面板的全站检索索引。请勿手改。 */
+window.NM_SEARCH = [{"s":"index","p":"./","g":"概览","n":"00","pg":"总览","h":"","t":"接在本地 Qwen3.5-4B 上的模型内记忆层。模型重启后不携带历史聊天记录,仍能访问已保存的个人与项目事实,且不把全部历史转换成 GPU 上的长 KV Cache。","d":1},{"s":"index","p":"./","g":"概览","n":"00","pg":"总览","h":"总览","t":"Natural Memory · NM2.1 1,048,576 槽位 · 738 条活跃记录 · 730 个索引桶 模型重启之后, 它仍然记得。 1.22 % → 82.52 % 同一 264 题题集的可回答正确率:无记忆基线 → 批次 B。 批次 A 为 62.60%,两批配置不同、不可合并。 wpyw.site · wpy@wpy.email · 本地 Qwen3.5-4B · 4-bit NF4 01 / 08 地址空间 1,0"},{"s":"index","p":"./","g":"概览","n":"00","pg":"总览","h":"把历史扔掉,它还记得吗?","t":"下面不是示意动画,是工程里两次真实运行的逐字回放。同一条事实、同一个问题、同一份协议; 两次之间唯一的差别,是写入有没有真的落库。 修复前 修复后 重放 写入的事实 重启之后问 记忆库记录 0 重启时传入的历史 false 注入模型内部的前缀 0 tok 模型输出 · 逐字 图 1 · 读取路径 一次问答里记忆层做了什么。用户问题只与少量页面和记录交互, 全程没有对全量记忆做注意力 ; 命中的短证据作为模型内部前缀注入 Qwen。图为示"},{"s":"index","p":"./","g":"概览","n":"00","pg":"总览","h":"这是什么东西","t":"它接在本地 Qwen3.5-4B 上,是一个 模型内的记忆层 :把适合长期复用的个人事实、项目事实和短对话片段 写进 带地址的记忆记录 ;当前问题只读取少量相关记录,再把这些记录作为 模型内部前缀 交给 Qwen 生成答案。 它要解决的具体问题是: 模型重启后不携带历史聊天记录,仍能访问已经保存的个人或项目事实, 同时不把全部历史转换成 GPU 上的长 KV Cache。 当前状态:研究原型到工程验证之间。同一 264 题题集上,可回答"},{"s":"index","p":"./","g":"概览","n":"00","pg":"总览","h":"发布之后又做了什么","t":"上面所有数字都来自 v2。之后的 NM2.1 换掉了记忆路由器、修掉写入路径上一个会把事实互相销毁的缺陷, 并把「问库里没有的属性时照样编答案」这个 75% 的失败修到 0%。 未知拒答率 · 24 条同形候选 100.00 % 从 0.00% 起 —— 修好之前,模型 75% 的情况下照样编一个答案 这是整个项目最该被引用的一个数字。它回答的不是「能不能检索到」,而是 「不知道的时候会不会不懂装懂」 。 未知拒答率 · 同形候选 0."},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"","t":"为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。","d":1},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"记忆与 KV 的分工","t":"这一节回答一个问题: 既然模型已经有上下文窗口,为什么还要单独做一层记忆? 答案不是「窗口不够长」,而是 把长期事实塞进上下文,会以线性成本换来极低的复用率 。 下面把「哪些东西留在 Qwen 的热 KV 里、哪些写进记忆记录」这条分界线逐层讲清楚。"},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"先看没有这层记忆时会发生什么","t":"同一套题、同一协议下,原版 Qwen3.5-4B 在 246 道 可回答 题上只答对 3 道(1.22%)。 它的典型回答不是答错,而是明确表示拿不到信息: 作为一个人工智能模型,我无法访问您的个人历史对话记录、系统配置或您之前设定的具体规则……每次对话对我来说都是全新的开始。 原版 Qwen3.5-4B · 评测样本 user-009 原始回答 这句话在事实上完全正确——模型确实没有这些信息。问题出在:这些信息 本来就应该被保存下来 "},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"四层分工:谁存什么、为什么","t":"这是整份设计里最核心的一张表。判断标准只有一条: 这份信息是「这一轮才有意义」还是「跨轮次可复用」 。 层级 存放什么 由谁负责 为什么放在这一层 最近对话 这一轮前后的原话与顺序 Qwen 热 KV 指代、省略、语气、\"你刚才说的那句\"都依赖严格的先后顺序。这类信息复用价值低、时效性强,放 KV 最自然。 长期个人 / 项目事实 事实、版本、来源、置信度、短文本证据 Natural Memory 记录 跨会话反复被问到,且需要保留\"哪"},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"分工落到数字上是什么样","t":"下面这组对照最能说明「不是把历史灌进去,而是把命中的少量证据灌进去」。两组数字分别来自批次 A 与批次 B(同一题集、同一协议)。 无记忆时的平均输入 30.75 token 批次 A 基线:只有问题本身,没有历史 有记忆时的平均输入 441.19 token 批次 B:命中的证据前缀(批次 A 为 641.42) 前缀命中率 96.97 % 批次 B 实际注入证据的题目占比(批次 A 为 90.91%) GPU cache 实际用量 "},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"这条分工线换来了什么、付出了什么","t":"换来的 可回答正确率 1.22% → 82.52% 目标记忆召回 0% → 92.68% 符号定位(192 题) 0/192 → 181/192 跨会话问题 1/6 → 5/6 付出的 平均输入前缀 30.75 → 441.19 token 平均总延迟(批次 B) 2854.9 → 2878.3 ms 平均解码速度(批次 B) 22.34 → 20.68 tok/s reserved 显存峰值 3.234 → 5.180 GiB"},{"s":"division","p":"./division","g":"原理","n":"01","pg":"记忆与 KV 的分工","h":"这条线不能推到哪里","t":"✕ Natural Memory 已经等价于百万 token 的完整 KV UNSUPPORTED ✕ 长上下文限制已被解除(实测 8K 可跑,16K / 32K OOM) UNSUPPORTED ✕ 设计容量 1,048,576 条记录等于已经装入并验证了这么多 UNSUPPORTED 第三条尤其要注意: 1,048,576 是地址空间与分页结构的容量 , 本次两个批次实际装载的是 723 / 738 条 active records"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"","t":"从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。","d":1},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"一次读取与一次写入","t":"这一节把「 一次读取 」和「 一次写入 」拆开讲透:读取路径有哪八个环节、每一步的输入输出是什么、 一条记录里到底存了什么、四个状态如何演化,以及我们为这些决定付出了什么代价。"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"一、读取路径:从提问到证据注入","t":"点左侧任一环节查看它在做什么,也可以依次点一遍——注意整条链路上 没有一步是「和全部记忆做注意力」 , 这是显存可控的根本原因。 STAGE 01 / 08"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"一次读取与一次写入","t":"读取上限 2 records 候选页上限 4 pages 页容量 32 / page 全量注意力 否"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"每一步的输入、输出与约束","t":"括号里的数字是实测值,不是设计目标。 环节 输入 → 输出 约束或实测 01 用户问题 普通自然语言 不要求任何显式指令,不需要输入 /remember 之类的标记。 02 Qwen hidden state 问题 → 紧凑查询地址 地址由模型隐状态生成,不依赖外部 embedding 服务;语义地址编码用单条 batch 执行,避免编码阶段出现显存瞬时峰值。 03 LSH / 地址粗索引 查询地址 → 候选页 精确桶 + Hammin"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"二、一条记录里到底存了什么","t":"记录不是一段文本,而是一条带地址、带版本关系、带状态的结构化数据。下面左侧是全部字段, 右侧是 一条真实记录 (逐字取自评测结果里的命中记录)。"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"三、状态机:旧的答案不会凭空消失","t":"同一实体和属性出现新值时, 旧值被标记为 superseded,而不是被覆盖或删除 。 这是「时间冲突」类问题能答对的前提:既要给出当前有效值,也要能说清旧值去哪了。 写入路径的教训:检索救不回不存在的记录 早期版本的退役授权完全依赖一个学习打分(阈值 ≥0.95), 没有任何结构校验 。 结果是新写入把已有记录误判为旧值并退役:一次 20 条记录的测试里 active 只剩 12 条、retract 高达 16 次、目标记录只存活 8"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"四、四个设计决定与它们的代价","t":"决定 1 · 两段式读取 粗索引筛页 → 页内重排 ,而不是对全量记录做注意力。 好处:单次读取的候选规模被压到固定上限(≤4 页 / ≤2 条),成本与记忆总量解耦。 代价:两段都可能出错,且错误会叠加——粗索引错了就再没有机会。 决定 2 · 用地址索引而非向量库 语义地址 + LSH 桶放在模型包内, 不引入外部检索服务 。 好处:部署时没有额外进程与网络依赖,读取耗时稳定(批次 B 26.35 ms)。 代价:召回质量受地址质量"},{"s":"mechanism","p":"./mechanism","g":"原理","n":"02","pg":"一次读取与一次写入","h":"五、这套机制仍然做不到的事","t":"✕ 任何自然语言表达都能稳定召回正确记忆 UNSUPPORTED ✕ 记忆写入永远不会出错 UNSUPPORTED ✕ 检索召回率等于回答正确率(批次 B 召回 92.68% / 回答 82.52%,差 10.16pp) UNSUPPORTED"},{"s":"results","p":"./results","g":"证据","n":"03","pg":"测量结果","h":"","t":"同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。","d":1},{"s":"results","p":"./results","g":"证据","n":"03","pg":"测量结果","h":"测量结果","t":"三个面板对应三组不同的题集与口径, 不能合并成一个总分 。 面板一是同一题集先后跑的两个独立批次(配置不同、分数不可合并,只能并列读); 面板二是结构化工程基准;面板三是记忆路由器工程线。每组数据都标注了出处、日期与口径。 读这些数字时请留意两件事: 所有对照都使用同一 tokenizer、同一 greedy 解码、同一 64 token 输出上限 ; 而 检索召回率与回答正确率是两个必须分开报告的量 ——这一点在面板一里体现得最清楚。"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"","t":"三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。","d":1},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"NM2.1 · 之后做了什么","t":"发布页上的数字来自 v2。这一页讲**之后又做了什么**:三处改动各自凭什么、一个把端到端正确率 锁死在 50% 的写入缺陷怎么定位的、以及一个 75% 泄漏的失败怎么修到 0%——又怎么在接上运行时之后失败两次。 所有数字都来自磁盘上的报告文件,每张表都标了出处;**没有一条是估算或外推**。 先读这一句 这一页里**离线实验最好的数字(未知拒答 100.00%)不等于运行时可用**。它接上运行时之后 连续失败两次,最终被回退。把失败"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"净变化一览","t":"出处 · V2_dpskw\\NM2_VS_NM2_1.md (该文件自述:所有数字均从磁盘上的评分卡 / JSON 读取,不是手工抄录) 轴 v2(原版) NM2.1 净变化 路由器 Top-1 41.12% 94.14% +53.02 pp 路由器 Recall@3 46.73% 96.48% +49.75 pp 未知拒答率 0.00% 100.00% +100.00 pp 未知问题泄漏(同形候选) 75.00% 0.00% −75."},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"一 · 路由器工件本身的检索与排序","t":"出处 · router_scorecard_final.json (判定见 router_verdict_final.json )· 冻结 v6 评测集 21,920 条 / 10 类别 指标 v2 · V2-128 deployed(v3) NM2.1 · REPLAY-128 v7 final Top-1 正确率 41.12% 94.14% Recall@1 37.03% 88.58% Recall@3 46.73% 96.48% "},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"二 · 拒答与仲裁策略轴","t":"出处 · router_scorecard_final.json (门槛 0.50)+ threshold_sweep_check.json (0.30–0.80 全门槛扫描) 指标 v2 NM2.1 need F1 89.58% 100.00% need 召回 99.82% 100.00% need 精确率 81.24% 100.00% 未知拒答率 0.00% 100.00% 已知问题被误拒率 0.18% 0.00% 未知问题被误读率"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"三 · 端到端整体记忆能力(同一份运行时,只有模型包不同)","t":"出处 · nm2_battery_comparison_final.json · A 110 用例 / B 16 / C 48 / D 重启持久化 指标 原版 NM2 NM2.1(仅换路由器) NM2.1 最终 A 总体正确率 89.09% 88.18% 88.18% A 可回答正确率 100.00% 100.00% 100.00% A 未知拒答率 60.00% 56.67% 56.67% A 已知问题被误拒率 0.00% 0.00% "},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"四 · 零字面重叠改写的泛化","t":"出处 · replay_check_zov.json (路由器级)与上表 B / C 段(端到端)· 数据集 300 条、24 个 未见过 的问法 指标 v2 权重 NM2.1 权重 路由器 Top-1(250 条可回答) 18.40% 59.60% 路由器 Recall@3 36.00% 83.20% 路由器 MRR 35.07% 73.06% 端到端改写正确率(B 段) 43.75% 68.75% 端到端未知泄漏(C 段) 75.0"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"五 · 代价:几乎没有","t":"出处 · router_scorecard_final.json 、 router_latency_bench_prod.json (7 轮交错取中位数) 指标 v2 NM2.1 说明 参数量 2,037,774 2,037,774 未增加 每条记录地址字节 512 512 存储几何未变 单查询延迟中位数(GPU) 0.9549 ms 0.9169 ms 略快 批量 QPS(batch=64) 66,037 69,378 +5.1% 批"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"六 · 工程鲁棒性","t":"项目 v2 NM2.1 一次写 20 条不同属性事实后存活 12 / 20(8 条查询前被误删) 20 / 20 端到端(写入修复前后,16 用例) 37.50% 68.75% 替换兼容性 基线 DROP-IN OK(16 / 16 键) 单元测试 — 52 项通过 未知问题泄漏(同形候选) 75.00% 0.00%"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"七 · 三处改动,每处都有测量依据","t":""},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"改动 1 · 记录排序混合权重取 0.5","t":"memory_record_router_blend 控制「打包的词面重排器」与「学习出的路由器」各占多少。 调这个之前先测了剂量曲线 —— 而且**此前的同进程测量因顺序污染已作废**,下面这版是独立进程测的。"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"改动 2 · 把加法先验参数化,然后测出「不该动」","t":"假设是「加法先验压过了学习打分」。把它暴露成可调再测,结果**调小只会更差**: 这是一个 有价值的负面结论 :它否定了原来的假设。项目里这类结论和增益一样被保留下来, 因为它们决定了后面不再往哪个方向投入。"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"改动 3 · 覆盖门 —— 以及为了让它真正生效必须一起修的两处","t":"覆盖门本身很简单:先判断「这个提问在问哪个属性」,再查库里有没有这个属性。但单独加门是不生效的, 有两处**必须同时修**,否则门会被静默绕过: 01"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"_build_text_prefix 短路","t":"门拒绝之后如果继续往下回落,会走**旧版 16 槽注入路径**,把记忆又塞回去。 实测:不修这一处,泄漏只从 75.00% 降到 62.50%。 02"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"闭包要动态解析 bank","t":"reset_memory() 每次都会新建 memory_os_v2 ;闭包捕获了旧引用的话, 门会永远看到一个空 bank,于是**静默旁路** —— 实测诊断输出 applicable:0 / bypassed:1 。 03"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"门必须自门控","t":"只有当库的属性集合填充了头部词表的 ≥ 90% 时才让门生效。 这条阈值是必需的:放宽成「子集即可」会让门在通用套件上误判, 把 A 段可回答正确率从 100.00% 压到 92.50%、已知误拒升到 2.50%;收紧到 90% 后 A 段完全恢复, C 段泄漏仍为 0.00%。"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"八 · 写入路径:为什么 20 条事实会互相销毁","t":"出处 · V2_dpskw\\WRITE_PATH_FIX.md (根因由栈追踪确证,非推断) 症状 一次写入 20 条 不同属性 的事实后,库里有 20 条记录但只剩 12 条 active , 8 条在查询前就被 retract。16 个关键零重叠用例里有 8 个的目标记录已经不存在 —— 因此端到端正确率存在 50% 的硬上限 。**任何排序器都救不回一条已经被删掉的记录。** 16 次 retract 的调用栈完全一致: call"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"修复(两处,都是结构性的)","t":"01"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"confirmed_update 的两条打分类分支改为结构优先","t":"仅当候选文本的 entity::attribute 已经在库的 active_by_conflict 账本里存在(即确实是同一条事实的更新)才允许由打分授权; exact_slots 这条结构安全的分支保持不变,以保留「重复写入幂等」的既有语义。 02"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"retract 循环加冲突键守卫","t":"if record.conflict_key() != candidate_conflict_key: continue —— 循环只能退掉 描述同一属性 的记录,不再因「共享热槽」或「词面重叠高」销毁无关事实。 语义不受损:真·同属性更新本来就已经由 PagedMemoryBankV2.write 通过 active_by_conflict 做版本化(旧版本保留为 superseded、 version+1 ), 修复只是移除了那条 "},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"四级验证","t":"级 检验 修复前 修复后 ① status_transition_summary {\"active->retracted\": 16} {} ① retraction_calls / status_counts 16 次 / {retracted: 8, active: 12} [] / {active: 20} ② 正对照:同属性改值 — 旧值 superseded + 新值 active,其余 19 属性不受影响,0 retract "},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"九 · 未知拒答:从 75% 泄漏到 100% 拒答","t":"出处 · V2_dpskw\\ABSTENTION_BREAKTHROUGH.md 这是整个项目最差的一个数字:问库里不存在的属性时,75% 的情况下模型照样编一个答案。 下面按「先排除一整类无效修法 → 再给出有效机制 → 最后如实记录接上运行时的两次失败」来写。"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"9.1 先排除掉一整类无效修法(有测量支撑)","t":"最直觉的修法是「检索分数不够高就拒答」。用 只在训练集拟合、评测集算 AUC 的口径, 测了四种打分器的分数几何(top1 / margin / top-k 均值 / 离散度 / 熵 / 近邻数,共 9 个特征): 打分器 最佳单特征 AUC 拟合头 AUC(评测集) cosine(冻结键余弦) 0.6257(margin) 0.6086 打包 text_retriever 0.6076(margin) 0.4752 50/50 混合 "},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"9.2 有效的机制:先判「问的是哪个属性」,再查库里有没有","t":"换一个问题问就成立了:不是「有没有候选匹配得好」,而是「这个提问在问哪个属性,这个属性在库里吗」。 封闭词表 值 可回答 episode 中「预测属性在候选集内」 100.00%(250 条) 未知 episode 中「预测属性在候选集内」 0.00%(50 条) 未知拒答率 100.00% 已知问题被误拒率 0.00% 混淆矩阵 tp=50 fn=0 fp=0 tn=250 开放集: 真实用户会问模型没见过的属性。只靠最大概率调阈值不"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"9.3 接上运行时:连续失败两次(如实记录)","t":"指标 NM2.1(无门) + 覆盖门(第一轮) + 门 + 短路(第二轮) A 可回答正确率 100.00% 6.25% 0.00% A 已知问题被误拒率 0.00% 7.50% 7.50% A 未知拒答率 56.67% 43.33% — C 未知泄漏率 75.00% 62.50% 0.00% C 可回答正确率 65.00% 65.00% 65.00% 第一轮: 门把几乎全部可回答问题都拒掉了 (100.00% → 6.25%),而它本"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"十 · 关键负面结论:路由器排序不影响端到端答案","t":"出处 · V2_dpskw\\ZERO_OVERLAP_FINDINGS.md §5 NM2.1 的路由器在零重叠集合上 Top-1 提升了 41.20 pp ,但在 eval_router_critical_e2e.py (16 个零重叠用例)上,REPLAY-128 与 V2-128-v6 的结果 逐位相同 (默认 Top-K:37.50% / 37.50%,答成别的属性 25.00% / 25.00%,平均选中记录 5.88 / 5"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"memory_os_v2.py:1671 :路由器分数被逐位置覆盖","t":"带 semantic_key 的记录,其 _score_candidates() 分数会被 self.record_scorer 覆盖。实测残差与 text_retriever 匹配 18 / 18, 与 memory_router_v2 匹配 0 / 18。 02"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"「部署目录没有 text_retriever.pt 所以走余弦兜底」是错的","t":"重排器被烘焙进合并包(safetensors 内有 6 个 dynamic_memory.text_retriever.* 张量,1,573,377 参数), _text_retriever_ready 实测为 True ,余弦兜底分支根本不执行。 03"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"路由器在此配置下唯一实际生效的杠杆是 need_memory 门","t":"部署权重下门开启率 68.75% (11 / 16),而三份随机初始化的路由器只有 37.5% (6 / 16)。判决性探针只随机化了排序通路、保留了门控头,所以「端到端零变化」 是由构造保证的,不是巧合。 结论分两层,不要混为一谈: ① 路由器记分卡上 22 / 22 的统治性和 +41.20 pp 的零重叠提升, 都是路由器工件 真实 的增益;② 但在这套运行时里,记录级顺序由打包的 text_retriever 决定, 路由器分数"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"十一 · 仍未解决(不粉饰)","t":"短板 现状 说明 跨域未知拒答 未解决 覆盖头是 24 类闭集,只在其词表被库填充 ≥ 90% 时生效;开放词表的属性匹配实测仅 49.20% Top-1 / AUC 0.6560 (零训练),不足 答成别的属性 27.50% 同形候选间排序仍不理想;已排除先验重加权(更差)与单纯替换打分器(更差) A 段未知拒答率 56.67% 未见改善 规模验证 未做 仅 24 属性 / 300 条评测;生产需上千属性、上万改写问法 V2 库记录真"},{"s":"nm21","p":"./nm21","g":"证据","n":"04","pg":"NM2.1 · 之后做了什么","h":"十二 · 复现","t":"PowerShell · 整体电池(A / B / C / D 四段) 复制 $env:PYTHONPATH='H:\\Memory'; $env:PYTHONIOENCODING='utf-8'; cd H:\\Memory\\V2_dpskw # 整体电池(A/B/C/D 四段) pwsh -NoProfile -File .\\run_nm2_battery.ps1 -Package qwen3_5_4b_natural_memory_v"},{"s":"examples","p":"./examples","g":"证据","n":"05","pg":"实测样例","h":"","t":"五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。","d":1},{"s":"examples","p":"./examples","g":"证据","n":"05","pg":"实测样例","h":"实测样例","t":""},{"s":"ledger","p":"./ledger","g":"证据","n":"06","pg":"真实评测台账","h":"","t":"全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。","d":1},{"s":"ledger","p":"./ledger","g":"证据","n":"06","pg":"真实评测台账","h":"真实评测台账","t":"这一页不是挑出来的样例,是 全部 108 条真实评测 episode 的逐条台账 。 每条都带模型的逐字回复、判定结果、停止原因、选中记录数与分数。 数据来自 runtime_score_bands.json 与 nm2_1_final_runtime_e2e.json , 由 tools/make_ledger.py 生成 assets/js/ledger.js , 字段逐字复制,未做改写、润色或换算 。模型输出里的错字、冗余和编造的"},{"s":"cost","p":"./cost","g":"工程","n":"07","pg":"代价账本","h":"","t":"记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。","d":1},{"s":"cost","p":"./cost","g":"工程","n":"07","pg":"代价账本","h":"代价账本","t":"VRAM · 12 GiB CARD allocated 与 reserved 均为每次请求结束后的快照, 不是 CUDA high-water mark ; reserved 还包含 PyTorch 分配器保留的缓存。因此这里不能当作\"模型峰值显存\"来引用。"},{"s":"capacity","p":"./capacity","g":"工程","n":"08","pg":"容量与存储","h":"","t":"地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。","d":1},{"s":"capacity","p":"./capacity","g":"工程","n":"08","pg":"容量与存储","h":"容量与存储","t":"「一百万记录容量」是 地址空间和分页结构的容量 ,不是本次已经装入了一百万条语义记忆, 也不是一百万条记录已经通过端到端测试。这一节把容量、实际用量与存储方式分开列清楚。 PAGE MAP · 32768 PAGES × 32 SLOTS 高亮部分为本次运行实际使用的页面(批次 A:23 页 / 723 条 active records;批次 B:24 页 / 738 条)。其余为地址空间余量。 PERSISTENCE"},{"s":"capacity","p":"./capacity","g":"工程","n":"08","pg":"容量与存储","h":"容量与存储","t":"TRADE-OFF"},{"s":"capacity","p":"./capacity","g":"工程","n":"08","pg":"容量与存储","h":"取舍","t":""},{"s":"limits","p":"./limits","g":"工程","n":"09","pg":"我们不宣称的内容","h":"","t":"没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。","d":1},{"s":"limits","p":"./limits","g":"工程","n":"09","pg":"我们不宣称的内容","h":"五项主要局限","t":""},{"s":"limits","p":"./limits","g":"工程","n":"09","pg":"我们不宣称的内容","h":"来自工程文档的原文","t":""},{"s":"roadmap","p":"./roadmap","g":"工程","n":"10","pg":"已解决的与下一步","h":"","t":"在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。","d":1},{"s":"roadmap","p":"./roadmap","g":"工程","n":"10","pg":"已解决的与下一步","h":"已解决的与下一步","t":"左侧是在 12 GiB GPU 上已经跑通的闭环;右侧是按 对正确率最有帮助的顺序 排出的下一阶段工程任务。 这里刻意不写时间表——顺序比日期更可信。"},{"s":"roadmap","p":"./roadmap","g":"工程","n":"10","pg":"已解决的与下一步","h":"已经解决的问题","t":""},{"s":"roadmap","p":"./roadmap","g":"工程","n":"10","pg":"已解决的与下一步","h":"下一阶段的工程任务","t":""},{"s":"reproduce","p":"./reproduce","g":"工程","n":"11","pg":"复现实验","h":"","t":"正式脏数据迁移测试的完整命令、协议参数与产物路径。","d":1},{"s":"reproduce","p":"./reproduce","g":"工程","n":"11","pg":"复现实验","h":"复现实验","t":"正式脏数据迁移测试的完整命令与产物路径。协议参数与批次之间的差异见下方说明—— 复现同一批次才能得到同一组数字 。 PowerShell · 工作目录 复制命令 PROTOCOL 基座模型 量化 4-bit NF4 解码 greedy 最大新生成 64 token 进程显存上限 10 GiB 记忆读取上限 2 records 地址编码 batch size 1 两个批次的差别 上面的命令复现的是批次 A(对外技术总结采用的口径,可答题 "}];
diff --git a/assets/js/site.js b/assets/js/site.js
new file mode 100644
index 0000000..a65e972
--- /dev/null
+++ b/assets/js/site.js
@@ -0,0 +1,1188 @@
+/* ==================================================================
+ Natural Memory v2 — site.js(多页版)
+ 共享外壳(顶栏读数、页脚、入场动效)+ 按 body[data-page] 分派的页面渲染。
+ 所有内容来自 window.NM_DATA / window.NM_EXAMPLES,保证与数据层单一来源。
+ ================================================================== */
+(function () {
+ "use strict";
+ var D = window.NM_DATA;
+ if (!D) return;
+
+ /* ---------- 工具 ---------- */
+ var $ = function (s, r) { return (r || document).querySelector(s); };
+ var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); };
+ var el = function (tag, cls, html) {
+ var n = document.createElement(tag);
+ if (cls) n.className = cls;
+ if (html != null) n.innerHTML = html;
+ return n;
+ };
+ var esc = function (s) {
+ return String(s == null ? "" : s).replace(/[&<>"]/g, function (c) {
+ return { "&": "&", "<": "<", ">": ">", '"': """ }[c];
+ });
+ };
+ var fmt = function (n, d) { return Number(n).toFixed(d == null ? 2 : d); };
+ var num = function (s) { var p = String(s).split("/"); return { c: Number(p[0]), n: Number(p[1]) }; };
+ var arrow = '→';
+ var setText = function (sel, v) { var n = $(sel); if (n) n.textContent = v; };
+ var copyText = function (btn, text) {
+ var done = function () {
+ btn.textContent = "已复制";
+ btn.classList.add("is-done");
+ setTimeout(function () { btn.textContent = "复制"; btn.classList.remove("is-done"); }, 1900);
+ };
+ var fallback = function () {
+ var ta = document.createElement("textarea");
+ ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0";
+ document.body.appendChild(ta); ta.select();
+ try { document.execCommand("copy"); done(); } catch (e) { btn.textContent = "复制失败"; }
+ document.body.removeChild(ta);
+ };
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text).then(done, fallback);
+ } else { fallback(); }
+ };
+
+ /* ---------- 入场与数值动画 ----------
+ 注意:这里**不把 IntersectionObserver 当唯一触发源**。
+ IO 在无头虚拟时间、被节流的 iframe、以及某些低端 WebView 里都不保证投递,
+ 而「内容卡在隐藏态」是绝不能出现的故障 —— 尤其是 clip-path 擦除,
+ 一次不触发就是一个永远看不见的标题。所以 IO 之外还有一条按滚动位置判定的兜底。 */
+ var pending = [];
+ var revealNow = function (node) {
+ if (!node || (node.classList && node.classList.contains("is-in"))) return;
+ node.classList.add("is-in");
+ $$("[data-fill]", node).forEach(function (f) { f.style.width = f.getAttribute("data-fill") + "%"; });
+ if (node.hasAttribute && node.hasAttribute("data-fill")) {
+ node.style.width = node.getAttribute("data-fill") + "%";
+ }
+ };
+ var io = ("IntersectionObserver" in window)
+ ? new IntersectionObserver(function (entries) {
+ entries.forEach(function (e) {
+ if (!e.isIntersecting) return;
+ revealNow(e.target);
+ io.unobserve(e.target);
+ var k = pending.indexOf(e.target);
+ if (k > -1) pending.splice(k, 1);
+ });
+ }, { rootMargin: "0px 0px -8% 0px", threshold: 0.06 })
+ : null;
+
+ /* 兜底:每帧/每次滚动再按位置判一次,进视口就放出来 */
+ var sweepPending = function () {
+ if (!pending.length) return;
+ var vh = window.innerHeight || document.documentElement.clientHeight;
+ pending = pending.filter(function (n) {
+ var r = n.getBoundingClientRect();
+ if (r.top < vh * 0.94 && r.bottom > -60) {
+ revealNow(n);
+ if (io) io.unobserve(n);
+ return false;
+ }
+ return true;
+ });
+ };
+ window.addEventListener("scroll", sweepPending, { passive: true });
+ window.addEventListener("resize", sweepPending);
+
+ var watch = function (node) {
+ if (!node) return;
+ if (io) { pending.push(node); io.observe(node); }
+ else { revealNow(node); return; }
+ sweepPending(); /* 已经在视口里的立刻放出来 */
+ };
+ var bar = function (cls, pct, label, valueHtml) {
+ return '";
+ };
+
+ /* ================= 共享外壳 ================= */
+ (function shell() {
+ var header = $("#site"), progress = $("#progress");
+ var onScroll = function () {
+ var y = window.scrollY || document.documentElement.scrollTop;
+ if (header) header.classList.toggle("is-stuck", y > 8);
+ var h = document.documentElement.scrollHeight - window.innerHeight;
+ if (progress) progress.style.width = (h > 0 ? Math.min(100, (y / h) * 100) : 0) + "%";
+ };
+ window.addEventListener("scroll", onScroll, { passive: true });
+ onScroll();
+
+ // 键盘导航:← / → 切换上一节、下一节(线性风格里的快捷键触感)
+ document.addEventListener("keydown", function (e) {
+ if (e.metaKey || e.ctrlKey || e.altKey || e.shiftKey) return;
+ var t = e.target;
+ if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
+ var sel = e.key === "ArrowLeft" ? ".pn__a--prev" : (e.key === "ArrowRight" ? ".pn__a--next" : null);
+ if (!sel) return;
+ var a = $(sel);
+ if (a && a.getAttribute("href")) { e.preventDefault(); location.href = a.getAttribute("href"); }
+ });
+
+ // 页脚:来源文件 + 免责声明
+ var ff = $("#footFiles");
+ if (ff) {
+ D.reproduce.files.forEach(function (x) {
+ ff.appendChild(el("span", "src", esc(x.v.split("\\").pop())));
+ });
+ }
+ setText("#disclaimer", D.meta.disclaimer);
+
+ // 服务端运行时读数
+ var chip = $("#serverChipText"), foot = $("#footServer");
+ fetch("api/status", { cache: "no-store" })
+ .then(function (r) { if (!r.ok) throw new Error("bad"); return r.json(); })
+ .then(function (s) {
+ if (chip) {
+ chip.textContent = "LIVE · UP " + s.uptime;
+ var c = $("#serverChip");
+ if (c) c.title = "本站服务端运行时读数 · bind " + s.bind;
+ }
+ if (foot) {
+ foot.innerHTML =
+ "host " + esc(s.host) + "" +
+ "bind " + esc(s.bind) + "" +
+ "python " + esc(s.python) + "" +
+ "uptime " + esc(s.uptime) + "" +
+ "requests " + esc(s.requests) + "";
+ }
+ })
+ .catch(function () {
+ if (chip) chip.textContent = "STATIC";
+ if (foot) foot.innerHTML = "运行时读数不可用(/api/status 无响应)";
+ });
+
+ /* ---------- 全站检索(命令面板) ----------
+ 索引由 build.py 生成到 assets/js/search.js,纯客户端,不发任何网络请求。
+ 快捷键:/ 或 Ctrl/Cmd+K 打开,Esc 关闭,↑↓ 选择,↵ 打开。 */
+ (function palette() {
+ var pal = $("#pal"), input = $("#palInput"), list = $("#palList"), n = $("#palN");
+ if (!pal || !input || !list) return;
+ var IDX = window.NM_SEARCH || [];
+ var HERE = document.body.getAttribute("data-page") || "";
+ var hits = [], cur = 0, open = false;
+
+ var low = function (s) { return String(s == null ? "" : s).toLowerCase(); };
+ function mark(text, q) {
+ if (!q) return esc(text);
+ var i = low(text).indexOf(q);
+ if (i < 0) return esc(text);
+ return esc(text.slice(0, i)) + "" + esc(text.slice(i, i + q.length)) + "" +
+ esc(text.slice(i + q.length));
+ }
+ function score(e, q) {
+ var h = low(e.h), t = low(e.t), p = low(e.pg), s = 0;
+ if (h === q) s += 100;
+ else if (h.indexOf(q) === 0) s += 60;
+ else if (h.indexOf(q) > -1) s += 40;
+ if (p.indexOf(q) > -1) s += 25;
+ if (t.indexOf(q) > -1) s += 14;
+ return s;
+ }
+ function query(raw) {
+ var q = low(raw).trim();
+ if (!q) return IDX.filter(function (e) { return e.d; }).map(function (e) { return { e: e, q: "" }; });
+ var out = [];
+ IDX.forEach(function (e) { var s = score(e, q); if (s > 0) out.push({ e: e, q: q, s: s }); });
+ out.sort(function (a, b) { return b.s - a.s || String(a.e.n).localeCompare(String(b.e.n)); });
+ return out.slice(0, 40);
+ }
+ function slugOf(href) { return href === "./" ? "index" : String(href).replace(/^\.\//, ""); }
+
+ function findHeading(text) {
+ var want = String(text).replace(/\s+/g, "");
+ var found = null;
+ $$("#doc h2, #doc h3").forEach(function (h) {
+ if (!found && h.textContent.replace(/\s+/g, "") === want) found = h;
+ });
+ return found;
+ }
+ function jumpTo(node) {
+ var reduce = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
+ var y = node.getBoundingClientRect().top + (window.scrollY || 0) - 84;
+ window.scrollTo({ top: y < 0 ? 0 : y, behavior: reduce ? "auto" : "smooth" });
+ node.classList.add("is-flash");
+ setTimeout(function () { node.classList.remove("is-flash"); }, 1700);
+ }
+
+ function render(q) {
+ hits = query(q);
+ cur = 0;
+ list.innerHTML = hits.length ? hits.map(function (h, i) {
+ var e = h.e, title = e.d ? e.pg : (e.h || e.pg);
+ var sub = e.h ? e.t : (e.d ? e.t : "");
+ return '' +
+ '' + esc(e.n) + "" +
+ '' + mark(title, h.q) + "" +
+ (sub ? '' + mark(sub, h.q) + "" : "") + "" +
+ '' + esc(e.g) + "";
+ }).join("") : '没有匹配的内容。试试「拒答」「写入路径」「召回」「SHA256」这类词。
';
+ if (n) n.textContent = hits.length ? hits.length + " 条" : "";
+ list.scrollTop = 0;
+ }
+
+ function select(i) {
+ var items = $$(".pal__i", list);
+ if (!items.length) return;
+ cur = Math.max(0, Math.min(items.length - 1, i));
+ items.forEach(function (el2, k) {
+ el2.classList.toggle("is-cur", k === cur);
+ el2.setAttribute("aria-selected", k === cur ? "true" : "false");
+ });
+ var it = items[cur];
+ if (it.offsetTop < list.scrollTop) list.scrollTop = it.offsetTop - 6;
+ else if (it.offsetTop + it.offsetHeight > list.scrollTop + list.clientHeight)
+ list.scrollTop = it.offsetTop + it.offsetHeight - list.clientHeight + 6;
+ }
+
+ function activate(a) {
+ var heading = a.getAttribute("data-h") || "";
+ var href = a.getAttribute("href") || "./";
+ var target = slugOf(href);
+ // 同页结果一律就地处理,绝不重新加载
+ if (target === HERE) {
+ var node = heading ? findHeading(heading) : null;
+ close();
+ if (node) { jumpTo(node); return; }
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ var hd = $("#doc .doc__hd") || $("#doc h1");
+ if (hd) {
+ hd.classList.add("is-flash");
+ setTimeout(function () { hd.classList.remove("is-flash"); }, 1700);
+ }
+ return;
+ }
+ try { sessionStorage.setItem("nm.pal", JSON.stringify({ s: target, h: heading })); }
+ catch (e) { /* 无存储时退化为普通跳转 */ }
+ // 键盘回车不会触发 的默认激活,所以这里显式跳转
+ location.href = href;
+ }
+
+ function show() {
+ open = true;
+ pal.hidden = false;
+ document.documentElement.classList.add("pal-open");
+ input.value = "";
+ render("");
+ input.focus();
+ }
+ function close() {
+ open = false;
+ pal.hidden = true;
+ document.documentElement.classList.remove("pal-open");
+ }
+
+ var btn = $("#palBtn"), mask = $("#palMask");
+ if (btn) btn.addEventListener("click", show);
+ if (mask) mask.addEventListener("click", close);
+ input.addEventListener("input", function () { render(input.value); });
+ list.addEventListener("mousemove", function (e) {
+ var a = e.target && e.target.closest ? e.target.closest(".pal__i") : null;
+ if (a) select(Number(a.getAttribute("data-i")));
+ });
+ list.addEventListener("click", function (e) {
+ var a = e.target && e.target.closest ? e.target.closest(".pal__i") : null;
+ if (!a) return;
+ e.preventDefault();
+ activate(a);
+ });
+ input.addEventListener("keydown", function (e) {
+ if (e.key === "ArrowDown") { e.preventDefault(); select(cur + 1); }
+ else if (e.key === "ArrowUp") { e.preventDefault(); select(cur - 1); }
+ else if (e.key === "Enter") {
+ var a = $$(".pal__i", list)[cur];
+ if (a) { e.preventDefault(); activate(a); }
+ }
+ });
+ document.addEventListener("keydown", function (e) {
+ var t = e.target;
+ var typing = !!(t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable));
+ if (e.key === "Escape" && open) { close(); if (t === input) input.blur(); return; }
+ if (open) return;
+ if ((e.ctrlKey || e.metaKey) && (e.key === "k" || e.key === "K")) { e.preventDefault(); show(); return; }
+ if (e.key === "/" && !typing && !e.ctrlKey && !e.metaKey && !e.altKey) { e.preventDefault(); show(); }
+ });
+
+ /* 从检索跨页跳进来时,落到对应小节并闪一下 */
+ try {
+ var saved = JSON.parse(sessionStorage.getItem("nm.pal") || "null");
+ sessionStorage.removeItem("nm.pal");
+ if (saved && saved.h && saved.s === HERE) {
+ var node2 = findHeading(saved.h);
+ if (node2) setTimeout(function () { jumpTo(node2); }, 60);
+ }
+ } catch (e) { /* 忽略 */ }
+ })();
+ })();
+
+ /* ================= 各页面渲染 ================= */
+ var PAGES = {};
+
+ /* ================= 重启实验装置 =================
+ 数据全部来自 window.NM_RESTART(逐字取自工程原始 JSON)。
+ 装置只做一件事:把「写入 → 重启 → 召回 → 作答」四个节拍的真实字段
+ 按顺序放出来。修复前 / 修复后 两个版本可以随时对照。
+ ================================================= */
+ function restartInstrument() {
+ var R = window.NM_RESTART, host = $("#rst");
+ if (!R || !host) return;
+
+ var beatsBox = $("#rstBeats"), playBtn = $("#rstPlay"), out = $("#rstOut");
+ var reduce = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
+ var vi = 0, timer = null, items = [];
+
+ /* 默认停在哪一版:由数据决定,不靠 HTML 里手写的 is-on(两者不一致过一次) */
+ R.variants.forEach(function (v, k) { if (v.key === "after") vi = k; });
+
+ /* --- 四个节拍的骨架 --- */
+ R.beats.forEach(function (b, i) {
+ var li = el("li", "rst__b");
+ li.dataset.i = i;
+ li.innerHTML =
+ '' + esc(b.i) + "" +
+ "" +
+ '' + esc(b.t) + "
" +
+ '' + esc(b.s) + "
" +
+ '' +
+ "";
+ li.addEventListener("click", function () { stop(); show(i); });
+ beatsBox.appendChild(li);
+ items.push(li);
+ });
+
+ /* --- 每个节拍要露出的真实字段 --- */
+ function beatData(v, i) {
+ var L = R.limits, after = v.key === "after";
+ if (i === 0) return [
+ ["automatic_write", "true"],
+ ["write_changed", String(v.writeChanged) + (v.writeChanged ? " → 记录已落库" : " → 记录没有落库")],
+ ["records", "0 → " + v.store.records],
+ ["coarse_index_buckets", "0 → " + v.store.coarse_index_buckets]
+ ];
+ if (i === 1) return [
+ ["history_passed_to_restart", "false → 重启不携带任何对话历史"],
+ ["resident_records", v.store.resident_records + " 条留在进程内存"],
+ ["gpu_cache records / tokens", v.store.gpu_cache_records + " / " + v.store.gpu_cache_tokens],
+ ["gpu_cache_bytes", v.store.gpu_cache_bytes + " B"],
+ ["容量上限", L.capacityRecords.toLocaleString("en-US") + " 条 · " + L.maxPages.toLocaleString("en-US") + " 页 × " + L.pageCapacity + " 条"]
+ ];
+ if (i === 2) {
+ if (!after) return [
+ ["need_memory", "false"],
+ ["stop_reason", v.stopReason],
+ ["confidence", fmt(v.confidence, 4)],
+ ["page_ids / record_ids", "(空)"]
+ ];
+ return [
+ ["need_memory", "true"],
+ ["page_ids", v.pageIds.join(", ") + " score " + fmt(v.pageScores[0], 4)],
+ ["record_ids", v.recordIds.join(", ") + " score " + fmt(v.recordScores[0], 4)],
+ ["hop_count", String(v.hopCount)],
+ ["confidence", fmt(v.confidence, 4)],
+ ["stop_reason", v.stopReason]
+ ];
+ }
+ return [
+ ["prefix_length", v.prefixLength + " tok (KV 预算 " + L.kvBudgetTokens.toLocaleString("en-US") + " tok)"],
+ ["prefix_used", String(v.prefixUsed)],
+ ["response", v.response],
+ ["router_recalled_after_restart", String(v.recalled)],
+ ["generated_contains_expected", String(v.correct)]
+ ];
+ }
+
+ function variant() { return R.variants[vi]; }
+
+ function fillData() {
+ var v = variant();
+ items.forEach(function (li, i) {
+ var d = $("#rstData" + i, li);
+ if (!d) return;
+ d.innerHTML = beatData(v, i).map(function (r) {
+ return '' + esc(r[0]) + "" + esc(r[1]) + "";
+ }).join("");
+ });
+ setText("#rstFile", (D.meta.repo || "") + "\\dynamic_memory_lab\\" + v.file);
+ }
+
+ function applyGauges(i) {
+ var v = variant(), L = R.limits;
+ var restarted = i >= 1, answered = i >= 3;
+
+ var rec = i >= 0 ? v.store.records : 0;
+ setText("#rstRec", String(rec));
+ $("#rstRecBar").style.width = (rec > 0 ? 100 : 0) + "%";
+
+ setText("#rstHist", restarted ? "false" : "true(本轮对话)");
+ $("#rstHistBar").style.width = (restarted ? 0 : 100) + "%";
+
+ var pf = answered ? v.prefixLength : 0;
+ $("#rstPf").innerHTML = pf + " tok";
+ var pct = pf ? (pf / L.kvBudgetTokens) * 100 : 0;
+ $("#rstPfBar").style.width = pf ? Math.max(0.8, pct) + "%" : "0%";
+ setText("#rstPfHint", pf
+ ? "占 KV 预算 " + pct.toFixed(2) + "% —— 记忆走的是前缀,不是把历史塞进上下文"
+ : "尚未注入证据前缀");
+
+ out.classList.toggle("is-idle", !answered);
+ var vd = $("#rstVerdict");
+ vd.className = "rst__verdict mono " + (answered ? (v.correct ? "is-ok" : "is-bad") : "");
+ vd.textContent = answered ? (v.correct ? "✓ 命中 " + R.expected : "✗ 未命中 " + R.expected) : "";
+ setText("#rstResp", answered ? v.response : "…");
+
+ var kv = $("#rstKv");
+ kv.innerHTML = answered ? [
+ ["stop_reason", v.stopReason, false],
+ ["prefix_used", String(v.prefixUsed), v.prefixUsed],
+ ["confidence", fmt(v.confidence, 4), v.confidence >= 0.7],
+ ["recalled_after_restart", String(v.recalled), v.recalled],
+ ["contains_expected", String(v.correct), v.correct]
+ ].map(function (r) {
+ return '' + esc(r[0]) + " " + esc(r[1]) + "";
+ }).join("") : "";
+
+ host.classList.toggle("is-payoff", answered && v.correct);
+ }
+
+ function show(i) {
+ items.forEach(function (li, j) {
+ li.classList.toggle("is-on", j === i);
+ li.classList.toggle("is-past", j < i);
+ li.setAttribute("aria-current", j === i ? "step" : "false");
+ });
+ applyGauges(i);
+ }
+
+ function stop() {
+ if (timer) { clearTimeout(timer); timer = null; }
+ playBtn.classList.remove("is-playing");
+ }
+
+ function play() {
+ stop();
+ show(-1);
+ if (reduce) { show(R.beats.length - 1); return; }
+ playBtn.classList.add("is-playing");
+ var i = 0;
+ var step = function () {
+ show(i);
+ i++;
+ if (i < R.beats.length) timer = setTimeout(step, 1050);
+ else { timer = null; playBtn.classList.remove("is-playing"); }
+ };
+ timer = setTimeout(step, 260);
+ }
+
+ playBtn.addEventListener("click", play);
+
+ $$(".seg__b", host).forEach(function (b, k) {
+ b.classList.toggle("is-on", k === vi);
+ b.addEventListener("click", function () {
+ vi = k;
+ $$(".seg__b", host).forEach(function (x) { x.classList.toggle("is-on", x === b); });
+ fillData();
+ play();
+ });
+ });
+
+ fillData();
+ show(-1);
+
+ // 进入视口后自动放一次。
+ // 用滚动位置判定而不是 IntersectionObserver 的可见比例:装置比视口高时
+ // 比例阈值可能永远达不到,这是很常见的坑。触发一次后即摘掉监听。
+ var played = false;
+ function autoPlay() {
+ if (played) return;
+ var r = host.getBoundingClientRect();
+ var vh = window.innerHeight || document.documentElement.clientHeight;
+ if (r.top < vh * 0.72 && r.bottom > vh * 0.08) {
+ played = true;
+ window.removeEventListener("scroll", autoPlay);
+ window.removeEventListener("resize", autoPlay);
+ play();
+ }
+ }
+ window.addEventListener("scroll", autoPlay, { passive: true });
+ window.addEventListener("resize", autoPlay);
+ autoPlay();
+ }
+
+ /* ---- 00 总览 ---- */
+ PAGES.index = function () {
+ setText("#heroLede", D.positioning.body);
+ setText("#heroSrc", D.meta.repo + "\\dynamic_memory_lab");
+ setText("#heroDisclaimer", D.meta.disclaimer);
+
+ var H = {};
+ D.dirty.headline.forEach(function (h) { H[h.metric] = h; });
+ var ans = H["可回答正确率"], rec = H["目标记忆召回率"];
+ var A = D.dirty.batches[0], B = D.dirty.batches[1];
+ var chain = function (h) {
+ return fmt(h.base) + "%" + arrow + '' + fmt(h.a) + "%" + arrow +
+ '' + fmt(h.b) + "%";
+ };
+ var items = [
+ { k: "可回答正确率 · 基线 → A → B", v: chain(ans),
+ d: "同一 264 题题集的两个独立批次。基线 " + ans.baseRaw + ",批次 A " + ans.aRaw + ",批次 B " + ans.bRaw + ";两批次配置不同,不可合并。" },
+ { k: "目标记忆召回率 · 基线 → A → B", v: chain(rec),
+ d: "基线没有记忆机制,召回恒为 0。批次 B 把该查的记忆查回来的比例达 " + rec.bRaw + "。" },
+ { k: "读取路径额外耗时 A → B", v: fmt(A.reader) + arrow + '' + fmt(B.reader) + " ms",
+ d: "记忆读取相对无记忆基线新增的耗时;批次 A " + fmt(A.reader) + " ms,批次 B 降到 " + fmt(B.reader) + " ms。" }
+ ];
+ var box = $("#heroKpis");
+ if (box) {
+ items.forEach(function (it) {
+ box.appendChild(el("div", "kpi",
+ '' + esc(it.k) + "" +
+ '' + it.v + "" +
+ '' + esc(it.d) + ""));
+ });
+ watch(box);
+ }
+ // 发布页:结果条、读取路径图绘制、三种抵达编排、重启装置
+ watch($("#releaseResult"));
+ watch($(".teaser"));
+ $$(".arr-wipe, .arr-num, .arr-cards").forEach(watch);
+ restartInstrument();
+
+ var cite = $("#citeCopy");
+ if (cite) {
+ var text = ($("#citeBlock") && $("#citeBlock").textContent) || "";
+ cite.addEventListener("click", function () {
+ var done = function () {
+ cite.textContent = "已复制"; cite.classList.add("is-done");
+ setTimeout(function () { cite.textContent = "复制"; cite.classList.remove("is-done"); }, 1900);
+ };
+ var fallback = function () {
+ var ta = document.createElement("textarea");
+ ta.value = text; ta.style.position = "fixed"; ta.style.opacity = "0";
+ document.body.appendChild(ta); ta.select();
+ try { document.execCommand("copy"); done(); } catch (e) { cite.textContent = "复制失败"; }
+ document.body.removeChild(ta);
+ };
+ if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, fallback);
+ else fallback();
+ });
+ }
+ };
+
+ /* ---- 02 机制 ---- */
+ PAGES.mechanism = function () {
+ // 读取路径步进器
+ (function flow() {
+ var steps = D.readPath, box = $("#steps");
+ if (!box) return;
+ steps.forEach(function (s, i) {
+ var b = el("button", "step",
+ '' + String(i + 1).padStart(2, "0") + "" +
+ '' + esc(s.label) + "");
+ b.type = "button";
+ b.dataset.i = i;
+ b.setAttribute("aria-selected", i === 0 ? "true" : "false");
+ b.addEventListener("click", function () { show(i); });
+ b.addEventListener("mouseenter", function () { show(i); });
+ box.appendChild(b);
+ });
+ function show(i) {
+ $$(".step", box).forEach(function (b) {
+ var j = Number(b.dataset.i);
+ b.classList.toggle("is-on", j === i);
+ b.classList.toggle("is-past", j < i);
+ b.setAttribute("aria-selected", j === i ? "true" : "false");
+ });
+ setText("#flowIdx", "STAGE " + String(i + 1).padStart(2, "0") + " / " + String(steps.length).padStart(2, "0"));
+ setText("#flowTitle", steps[i].label);
+ setText("#flowBody", steps[i].detail);
+ setText("#flowIo", steps[i].io || "");
+ }
+ show(0);
+ watch(box);
+ })();
+
+ // 记录字段
+ var rf = $("#recFields");
+ if (rf) {
+ D.recordFields.forEach(function (f) {
+ rf.appendChild(el("div", "rec__f",
+ '' + esc(f.f) + "" +
+ '' + esc(f.d) + ""));
+ });
+ watch(rf);
+ }
+
+ // 真实记录示例(取自 examples.js,逐字)
+ var rr = $("#realRecord");
+ var E = window.NM_EXAMPLES || [];
+ if (rr && E.length && E[0].nm.records && E[0].nm.records.length) {
+ var r = E[0].nm.records[0];
+ var rows = [
+ ["record_id", r.record_id], ["entity", r.entity], ["attribute", r.attribute],
+ ["value", r.value], ["status", r.status], ["version", r.version],
+ ["source", r.source], ["text_preview", r.text_preview]
+ ].filter(function (x) { return x[1] !== undefined && x[1] !== null && x[1] !== ""; });
+ rr.innerHTML = '一条真实记录 · 逐字取自评测结果
' +
+ '' + rows.map(function (x) {
+ return '' + esc(x[0]) + '' +
+ '' + esc(x[1]) + "";
+ }).join("") + "" +
+ '这条记录在评测中命中了问题 user-004,' +
+ '内容是用户给当时模型定的名字。注意「Natural Memory v1」是写入时的项目名,记录按原样保留、未做改写。
';
+ } else if (rr) {
+ rr.textContent = "真实记录示例不可用(examples.js 未加载)。";
+ }
+
+ // 状态机
+ var sl = $("#stateList");
+ if (sl) {
+ D.states.forEach(function (s) {
+ sl.appendChild(el("div", "state state--" + s.tone,
+ '' + esc(s.s) + "" +
+ '' + esc(s.d) + ""));
+ });
+ watch(sl);
+ }
+ };
+
+ /* ---- 03 测量结果 ---- */
+ PAGES.results = function () {
+ var tabs = $("#resultTabs"), panels = $("#resultPanels");
+ if (!tabs || !panels) return;
+ var DT = D.dirty;
+
+ var defs = [
+ { id: "dirty", label: "批次 A / B 对照" },
+ { id: "stage5", label: "Stage 5 结构化基准" },
+ { id: "router", label: "记忆路由器工程线" }
+ ];
+ var want = (new URLSearchParams(location.search).get("tab") || "").toLowerCase();
+ var startId = defs.some(function (d) { return d.id === want; }) ? want : defs[0].id;
+
+ var activate = function (id, push) {
+ $$(".tab", tabs).forEach(function (x) {
+ var on = x.dataset.id === id;
+ x.classList.toggle("is-on", on);
+ x.setAttribute("aria-selected", on ? "true" : "false");
+ });
+ $$(".panel", panels).forEach(function (p) {
+ var on = p.dataset.id === id;
+ p.classList.toggle("is-on", on);
+ p.setAttribute("aria-hidden", on ? "false" : "true");
+ if (on) $$("[data-fill]", p).forEach(function (f) { f.style.width = f.getAttribute("data-fill") + "%"; });
+ });
+ if (push) {
+ try {
+ var u = new URL(location.href);
+ u.searchParams.set("tab", id);
+ history.replaceState(null, "", u.pathname + u.search + u.hash);
+ } catch (e) { /* 仅影响地址栏 */ }
+ }
+ };
+
+ defs.forEach(function (d) {
+ var t = el("button", "tab" + (d.id === startId ? " is-on" : ""), esc(d.label));
+ t.type = "button"; t.setAttribute("role", "tab"); t.dataset.id = d.id;
+ t.setAttribute("aria-selected", d.id === startId ? "true" : "false");
+ t.addEventListener("click", function () { activate(d.id, true); });
+ tabs.appendChild(t);
+ });
+
+ /* --- 面板 1 --- */
+ var p1 = el("div", "panel"); p1.dataset.id = "dirty";
+ var h = '' +
+ "" + esc(DT.title) + " · " + esc(DT.subtitle) + "
" +
+ '' + esc(DT.protocol) + "
" +
+ '' +
+ '出处 ' + esc(DT.src) + "
" + esc(DT.batchNote) + "
";
+
+ h += '' +
+ '原版 Qwen3.5-4B' +
+ '批次 A' +
+ '批次 B';
+
+ h += '";
+ h += '' + DT.corpus.map(function (c) {
+ return '' + esc(c.k) + '' + esc(c.v) + "";
+ }).join("") + "";
+
+ h += '两个批次的运行明细
' +
+ '指标 ' +
+ DT.batches.map(function (b) {
+ return "" + esc(b.label) + '
' + esc(b.date) + " · " + esc(b.tag) + " ";
+ }).join("") + " ";
+ [
+ { k: "读取路径额外耗时", v: function (b) { return b.reader + " ms"; } },
+ { k: "平均输入前缀 token", v: function (b) { return fmt(b.promptTokens); } },
+ { k: "前缀命中率 / 平均前缀 token", v: function (b) { return fmt(b.prefixRate) + "% / " + fmt(b.prefixTokens); } },
+ { k: "平均总延迟(基线 → 记忆)", v: function (b) { return b.latencyBase + " → " + b.latencyNm + " ms"; } },
+ { k: "平均解码速度(基线 → 记忆)", v: function (b) { return b.decodeBase + " → " + b.decodeNm + " tok/s"; } },
+ { k: "NM 显存快照峰值 allocated / reserved", v: function (b) { return fmt(b.allocNm, 3) + " / " + fmt(b.resvNm, 3) + " GiB"; } },
+ { k: "active records / pages", v: function (b) { return b.records + " / " + b.pages; } },
+ { k: "GPU cache token", v: function (b) { return b.cacheTokens.toLocaleString("en-US"); } }
+ ].forEach(function (r) {
+ h += "" + esc(r.k) + " " + DT.batches.map(function (b) {
+ return '' + esc(r.v(b)) + " ";
+ }).join("") + " ";
+ });
+ h += "
";
+
+ h += '按数据域拆分(可回答正确率)
';
+ DT.domains.forEach(function (d) {
+ h += '";
+ });
+ h += "";
+
+ h += '按类别拆分(正确 / 题量)
' +
+ '类别 题量 原版 Qwen 批次 A 批次 B B 相对 A ';
+ DT.categories.forEach(function (c) {
+ var na = num(c.a), nb = num(c.b), diff = nb.c - na.c;
+ h += "" + esc(c.label) + '
' + esc(c.k) + " " +
+ '' + c.n + ' ' + esc(c.base) + ' ' + esc(c.a) + " " +
+ '' + esc(c.b) + " " +
+ '' + (diff > 0 ? "+" : "") + diff + " 题 ";
+ });
+ h += "
" + esc(DT.categoryNote) + "
";
+
+ h += '路由与生成的差距
' +
+ '' +
+ '批次 A · 目标记忆召回' + fmt(DT.gap.recallA) + '%' + esc(DT.gap.recallARaw) + "" +
+ '批次 B · 目标记忆召回' + fmt(DT.gap.recallB) + '%' + esc(DT.gap.recallBRaw) + "" +
+ '批次 A · 最终回答正确' + fmt(DT.gap.answerA) + '%' + esc(DT.gap.answerARaw) + "" +
+ '批次 B · 最终回答正确' + fmt(DT.gap.answerB) + '%' + esc(DT.gap.answerBRaw) + "" +
+ '' + esc(DT.gap.note) + "
";
+ p1.innerHTML = h;
+
+ /* --- 面板 2:Stage 5 --- */
+ var p2 = el("div", "panel"); p2.dataset.id = "stage5";
+ var h2 = '' +
+ esc(D.stage5.title) + " · " + esc(D.stage5.subtitle) +
+ '
出处 ' + esc(D.stage5.src) + "
" +
+ '' +
+ '原版 Qwen3.5-4B强 RAG 基线Natural Memory v2' +
+ '';
+ D.stage5.sets.forEach(function (set) {
+ h2 += '' + esc(set.name) + " · " + set.n + " 题
";
+ });
+ h2 += "";
+ h2 += '' +
+ "题集 系统 正确率 原始 平均延迟 解码速度 未知拒答 ";
+ D.stage5.sets.forEach(function (set) {
+ set.rows.forEach(function (r, i) {
+ h2 += "" + (i === 0 ? '' + esc(set.name) + " · " + set.n + " " : "") +
+ "" + esc(r.sys) + ' ' + fmt(r.acc) + "% " +
+ '' + esc(r.raw) + ' ' + r.lat + ' ms ' + r.tok + " tok/s " +
+ '' + (r.abstain ? esc(r.abstain) : "—") + " ";
+ });
+ });
+ h2 += "
" + esc(D.stage5.note) + "
";
+ p2.innerHTML = h2;
+
+ /* --- 面板 3:路由器工程线 --- */
+ var p3 = el("div", "panel"); p3.dataset.id = "router";
+ var h3 = '' +
+ esc(D.router.title) + " · " + esc(D.router.subtitle) +
+ '
出处 ' + esc(D.router.src) + "
" +
+ '' +
+ "指标 原 512 维 / 基线 128 维路由器 备注 " +
+ D.router.metrics.map(function (m) {
+ return "" + esc(m.k) + ' ' + esc(m.base) + ' ' +
+ esc(m.nm2) + ' ' + esc(m.note || "—") + " ";
+ }).join("") + "
" +
+ '' + D.router.spec.map(function (s) {
+ return '' + esc(s.k) + '' + esc(s.v) + "";
+ }).join("") + "" +
+ '' +
+ '这张表必须连这些一起读
' +
+ D.router.caveats.map(function (c, i) {
+ return '' + String(i + 1).padStart(2, "0") +
+ '' + esc(c) + "
";
+ }).join("") + "";
+ p3.innerHTML = h3;
+
+ panels.appendChild(p1); panels.appendChild(p2); panels.appendChild(p3);
+ [p1, p2, p3].forEach(function (p) { p.setAttribute("role", "tabpanel"); p.setAttribute("aria-hidden", "true"); });
+ watch(p1);
+ activate(startId, false);
+ };
+
+ /* ---- 04 实测样例 ---- */
+ PAGES.examples = function () {
+ var box = $("#exampleList");
+ if (!box) return;
+ var E = window.NM_EXAMPLES || [];
+ setText("#exampleIntro", D.examples.subtitle);
+ setText("#exampleNote", D.examples.note);
+ if (!E.length) { box.appendChild(el("p", "small", "样例数据未加载。")); return; }
+
+ var DOMAIN = { user_dialogue: "用户对话事实", real_repository: "真实源码/文档" };
+ var CAT = {
+ symbol_navigation: "符号定位", unknown_refusal: "未知拒答", paraphrase: "自然改写",
+ constraint: "约束类问题", cross_session: "跨会话", temporal_conflict: "时间冲突"
+ };
+ var TOKMAX = 64;
+ var tok = function (t) {
+ return t >= TOKMAX ? '' + t + " token(达上限截断)" : t + " token";
+ };
+ var verdict = function (ok) {
+ return '' + (ok ? "命中" : "未命中") + "";
+ };
+ var col = function (title, cls, r, meta, extra) {
+ return '' +
+ '' + esc(title) + "
" +
+ '' + esc(r.response || "(空)") + "
" +
+ '" + (extra || "") + "";
+ };
+
+ E.forEach(function (e, i) {
+ var identical = (e.nm.response || "") === (e.base.response || "");
+ var ev = "";
+ if (e.nm.records && e.nm.records.length) {
+ ev = '命中的记忆记录
' +
+ e.nm.records.map(function (r) {
+ return '' +
+ '' + esc(r.attribute || r.entity || r.record_id) + "" +
+ '' + esc(r.text_preview || r.value || "") + "" +
+ '' + esc(r.record_id) + " · " + esc(r.source || "") +
+ (r.status ? " · " + esc(r.status) : "") + "";
+ }).join("") + "";
+ } else if (!e.answerable) {
+ ev = '命中的记忆记录
' +
+ '无 —— 该题目标本身不在库里,考察的是模型会不会硬答。';
+ }
+ box.appendChild(el("article", "ex reveal",
+ '' +
+ '' + String(i + 1).padStart(2, "0") + "" +
+ '' + esc(DOMAIN[e.domain] || e.domain) + " · " + esc(CAT[e.category] || e.category) + "" +
+ (e.answerable ? "" : '未知题') +
+ (identical ? '两系统回答逐字相同' : "") +
+ 'id=' + esc(e.id) + "" +
+ '' + esc(e.query) + "
" +
+ '期望锚点' + (e.expected
+ ? esc(e.expected) + (e.expected_anchors && e.expected_anchors.length > 1
+ ? " · 其余:" + esc(e.expected_anchors.slice(1).join("、")) : "")
+ : "无(未知题,考察拒答)") + "
" +
+ '' +
+ col("原版 Qwen3.5-4B", "base", e.base, "判分 " + verdict(e.base.correct)) +
+ col("Natural Memory v2", "nm", e.nm,
+ "判分 " + verdict(e.nm.correct) + (e.answerable ? " · 目标召回 " + (e.nm.recall ? "命中" : "未命中") : ""), ev) +
+ ""));
+ });
+ $$(".ex", box).forEach(watch);
+ };
+
+ /* ---- 05 代价 ---- */
+ PAGES.cost = function () {
+ setText("#costIntro", D.cost.subtitle);
+ setText("#costQuote", D.cost.quote);
+ var box = $("#ledger");
+ if (box) {
+ box.appendChild(el("div", "ledger__h", "指标原版 QwenNM v2变化"));
+ D.cost.rows.forEach(function (r) {
+ box.appendChild(el("div", "ledger__r",
+ '' + esc(r.k) + "" +
+ '' + esc(r.base) + "" +
+ '' + esc(r.nm2) + "" +
+ '' + esc(r.delta) + ""));
+ });
+ watch(box);
+ }
+ var v = D.cost.vram, vb = $("#vram");
+ if (vb) {
+ [
+ { k: "allocated · 无记忆基线", val: v.allocBase, cls: "base" },
+ { k: "allocated · NM v2", val: v.allocNm2, cls: "nm2" },
+ { k: "reserved · 无记忆基线", val: v.resvBase, cls: "base" },
+ { k: "reserved · NM v2", val: v.resvNm2, cls: "nm2" }
+ ].forEach(function (r) {
+ vb.innerHTML += '' + esc(r.k) + "" +
+ fmt(r.val, 3) + " " + v.unit + "" +
+ '';
+ });
+ vb.innerHTML += '012 GiB 显卡';
+ watch(vb);
+ }
+ };
+
+ /* ---- 06 容量 ---- */
+ PAGES.capacity = function () {
+ var box = $("#capList");
+ if (box) {
+ D.capacity.stats.forEach(function (s) {
+ var hi = /设计容量|GPU cache 上限/.test(s.k);
+ box.appendChild(el("div", "cap__r" + (hi ? " cap__r--hi" : ""),
+ '' + esc(s.k) + (s.d ? "" + esc(s.d) + "" : "") + "" +
+ '' + esc(s.v) + ""));
+ });
+ watch(box);
+ }
+ var g = $("#pageMap"), total = 512, used = D.capacity.grid.usedPages;
+ if (g) {
+ var step = Math.max(1, Math.floor(total / used)), out = "";
+ for (var i = 0; i < total; i++) {
+ var on = i % step === 0 && (i / step) < used;
+ out += "";
+ }
+ g.innerHTML = out;
+ }
+ setText("#storageMode", D.capacity.storage.mode);
+ setText("#storageText", D.capacity.storage.text);
+ setText("#storageTradeoff", D.capacity.storage.tradeoff);
+ };
+
+ /* ---- 07 边界 ---- */
+ PAGES.limits = function () {
+ setText("#limitsIntro", D.limits.subtitle);
+ var c = $("#claims");
+ if (c) {
+ D.limits.claims.forEach(function (t, i) {
+ c.appendChild(el("div", "claim",
+ '✕' + esc(t) + "" +
+ 'UNSUPPORTED ' + String(i + 1).padStart(2, "0") + ""));
+ });
+ watch(c);
+ }
+ var l = $("#lims");
+ if (l) {
+ D.limits.limitations.forEach(function (x, i) {
+ l.appendChild(el("div", "lim",
+ '' + String(i + 1).padStart(2, "0") + "" +
+ "" + esc(x.t) + '
' + esc(x.d) + "
"));
+ });
+ watch(l);
+ }
+ var q = $("#quotes");
+ if (q) {
+ D.limits.quotes.forEach(function (x) {
+ q.appendChild(el("div", "quote", "" + esc(x.q) + "
" + esc(x.s) + ""));
+ });
+ watch(q);
+ }
+ };
+
+ /* ---- 08 路线图 ---- */
+ PAGES.roadmap = function () {
+ var s = $("#solved");
+ if (s) {
+ D.solved.forEach(function (t, i) {
+ s.appendChild(el("div", "check",
+ '✓ ' + String(i + 1).padStart(2, "0") + "" +
+ '' + esc(t) + ""));
+ });
+ watch(s);
+ }
+ var r = $("#roadmap-list");
+ if (r) {
+ D.roadmap.forEach(function (t, i) {
+ r.appendChild(el("div", "road__r",
+ '→ ' + String(i + 1).padStart(2, "0") + "" +
+ '' + esc(t) + ""));
+ });
+ watch(r);
+ }
+ };
+
+ /* ---- 09 复现 ---- */
+ PAGES.reproduce = function () {
+ var R = D.reproduce;
+ setText("#cwd", R.cwd);
+ setText("#pBase", D.meta.base);
+ var cmd = R.cmd.join("\n"), block = $("#cmdBlock");
+ if (block) {
+ block.innerHTML = esc(cmd)
+ .replace(/(--[a-z-]+)/g, '$1')
+ .replace(/("[^&]*")/g, '$1');
+ }
+ var f = $("#files");
+ if (f) {
+ R.files.forEach(function (x) {
+ f.appendChild(el("div", "kv__r",
+ '' + esc(x.k) + '' + esc(x.v) + ""));
+ });
+ watch(f);
+ }
+ var btn = $("#copyBtn");
+ if (btn) {
+ btn.addEventListener("click", function () {
+ var done = function () {
+ btn.textContent = "已复制";
+ btn.classList.add("is-done");
+ setTimeout(function () { btn.textContent = "复制命令"; btn.classList.remove("is-done"); }, 1900);
+ };
+ var fallback = function () {
+ var ta = document.createElement("textarea");
+ ta.value = cmd; ta.style.position = "fixed"; ta.style.opacity = "0";
+ document.body.appendChild(ta); ta.select();
+ try { document.execCommand("copy"); done(); } catch (e) { btn.textContent = "复制失败"; }
+ document.body.removeChild(ta);
+ };
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(cmd).then(done, fallback);
+ } else { fallback(); }
+ });
+ }
+ };
+
+ /* ---- 04 NM2.1 ---- */
+ function doseChart(host, d) {
+ if (!host || !d) return;
+ var head = '' + esc(d.axis) + "" +
+ '' + esc(d.xLabel) + "";
+ var axis = '' +
+ d.x.map(function (v) {
+ return '' + esc(String(v)) + "";
+ }).join("") + "";
+ var rows = d.metrics.map(function (m) {
+ var max = Math.max.apply(null, m.v) || 1;
+ return '' + esc(m.label) +
+ "" + (m.higherBetter ? "越高越好" : "越低越好") + "" +
+ '' + m.v.map(function (v, i) {
+ var pick = d.x[i] === d.pick;
+ return '' +
+ '' +
+ "" + v.toFixed(2) + esc(m.unit) + "";
+ }).join("") + "";
+ }).join("");
+ host.innerHTML = head + axis + rows +
+ '' + esc(d.note) + "
" +
+ '' + esc(window.NM21 ? window.NM21.srcLabel : "") + "
";
+ }
+
+ PAGES.nm21 = function () {
+ var M = window.NM21;
+ if (M) {
+ doseChart($("#nmBlend"), M.blend);
+ doseChart($("#nmPrior"), M.prior);
+ }
+ var b = $("#nmCopy"), cmd = $("#nmCmd");
+ if (b && cmd) b.addEventListener("click", function () { copyText(b, cmd.textContent || ""); });
+ };
+
+ /* ---- 06 真实评测台账 ---- */
+ PAGES.ledger = function () {
+ var L = window.NM_LEDGER, box = $("#ledgerList");
+ if (!box || !L) return;
+
+ var total = 0, all = [];
+ L.forEach(function (s) {
+ total += s.n;
+ s.items.forEach(function (it) { all.push({ s: s, it: it }); });
+ });
+ setText("#ledgerIntro", "共 " + total + " 条真实 episode,来自 " + L.length +
+ " 次正式运行(24 条同句式候选、值为随机码,随机猜中概率 4.17%)。");
+
+ var verdictOf = function (it) {
+ if (!it.ans) return it.leak ? "leak" : "abstain";
+ if (it.ok) return "ok";
+ return it.wrong ? "wrong" : "miss";
+ };
+ var V = {
+ ok: { label: "答对", tone: "ok" },
+ wrong: { label: "答成别的属性", tone: "no" },
+ miss: { label: "答错", tone: "no" },
+ leak: { label: "未知泄漏", tone: "no" },
+ abstain: { label: "未知已拒答", tone: "ok" }
+ };
+
+ var fSrc = "all", fV = "all";
+
+ var srcDefs = [{ k: "all", label: "全部来源" }].concat(L.map(function (s) {
+ return { k: s.key, label: s.label + " · " + s.n };
+ }));
+ var vDefs = [{ k: "all", label: "全部判定" }].concat(["ok", "wrong", "miss", "leak", "abstain"].map(function (k) {
+ return { k: k, label: V[k].label };
+ }));
+
+ function buildSeg(host, defs, get, set) {
+ host.innerHTML = "";
+ defs.forEach(function (d) {
+ var b = el("button", "seg__b" + (get() === d.k ? " is-on" : ""), esc(d.label));
+ b.type = "button";
+ b.addEventListener("click", function () {
+ set(d.k);
+ $$(".seg__b", host).forEach(function (x, i) { x.classList.toggle("is-on", defs[i].k === d.k); });
+ paint();
+ });
+ host.appendChild(b);
+ });
+ }
+
+ function paint() {
+ var rows = all.filter(function (r) {
+ if (fSrc !== "all" && r.s.key !== fSrc) return false;
+ if (fV !== "all" && verdictOf(r.it) !== fV) return false;
+ return true;
+ });
+ var tally = { ok: 0, wrong: 0, miss: 0, leak: 0, abstain: 0 };
+ rows.forEach(function (r) { tally[verdictOf(r.it)]++; });
+ setText("#ledgerCount", "显示 " + rows.length + " / " + total + " 条");
+ setText("#ledgerBreak", Object.keys(V).map(function (k) {
+ return V[k].label + " " + tally[k];
+ }).join(" · "));
+
+ box.innerHTML = rows.length ? rows.map(function (r, i) {
+ var it = r.it, v = V[verdictOf(it)];
+ var src = r.s.note;
+ return '' +
+ '' +
+ '' + String(i + 1).padStart(3, "0") + "" +
+ '' + esc(v.label) + "" +
+ '属性 · ' + esc(it.attr || "—") + "" +
+ (it.ans ? "" : '未知题') +
+ '' + esc(it.id) + " · " + esc(src) + "" +
+ "" +
+ '期望锚点' +
+ (it.ans ? "" + esc(it.exp) + "" : "无(未知题,考察拒答)") + "
" +
+ '' + esc(it.reply || "(空)") + "
" +
+ '" +
+ " ";
+ }).join("") : '当前筛选下没有记录。
';
+ }
+
+ buildSeg($("#ledgerSrc"), srcDefs, function () { return fSrc; }, function (k) { fSrc = k; });
+ buildSeg($("#ledgerVerdict"), vDefs, function () { return fV; }, function (k) { fV = k; });
+ paint();
+ };
+
+ /* ---------- 分派 ---------- */
+ var page = document.body.getAttribute("data-page") || "";
+ if (PAGES[page]) {
+ try { PAGES[page](); }
+ catch (err) {
+ if (window.console) console.error("[NM] 页面渲染失败: " + page, err);
+ }
+ }
+})();
diff --git a/assets/js/space.js b/assets/js/space.js
new file mode 100644
index 0000000..e4c41f7
--- /dev/null
+++ b/assets/js/space.js
@@ -0,0 +1,256 @@
+/* ==================================================================
+ Natural Memory — space.js
+ 「地址空间下降」—— 本页的招牌机制。
+
+ 机制来自题材自身的物理(motion-web: design-slop B5):这套系统的读取路径
+ 本来就是一次寻址下降 —— 问题 → 128 维地址 → LSH 粗索引 → 候选页 →
+ 页内重排 → Top-K → 证据前缀 → 生成。所以镜头一路下降,而不是在旁边飘粒子。
+
+ 实现取 case 的**机制**、不取 case 的皮(cases/ 的照搬边界):
+ 滚动进度从文档里读回来((scrollY - offsetTop) / 可滚动距离),不用 sticky 累加 ——
+ 同 press-stack 的做法;原生滚动完全不动它。
+
+ 全部数字都是真的:1,048,576 槽位 / 738 条活跃记录 / 730 个索引桶 /
+ 24 页 × 页容量 32 / 最多 2 条记录 / 前缀 45 token。
+ ================================================================== */
+(function () {
+ "use strict";
+
+ var cv = document.getElementById("spaceCv");
+ if (!cv) return;
+
+ var D = window.NM_DATA;
+ var ctx = cv.getContext("2d");
+ var host = document.getElementById("hero");
+ var pin = cv.parentNode;
+
+ var reduce = !!(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
+ var narrow = window.matchMedia && window.matchMedia("(max-width: 900px)").matches;
+
+ /* ---- 真实的地址空间几何 ---- */
+ var GEO = {
+ slots: 1048576, /* 地址空间设计容量 */
+ records: 738, /* 批次 B 实际装载的 active records */
+ buckets: 730, /* LSH 粗索引桶数 */
+ pages: 24, /* 实际使用的页数 */
+ pageCap: 32, /* 页容量 */
+ topK: 2, /* 单次读取最多记录数 */
+ prefix: 45 /* 注入模型内部的前缀 token 数 */
+ };
+
+ /* ---- 可复现的伪随机(不用 Math.random:同一张图每次都一样) ---- */
+ function rng(seed) {
+ var s = seed >>> 0;
+ return function () {
+ s = (s * 1664525 + 1013904223) >>> 0;
+ return s / 4294967296;
+ };
+ }
+
+ var N = 128; /* 空间栅格 128×128 = 16,384 格 */
+ var SPACE = 1024; /* 离屏位图边长 */
+ var CELL = SPACE / N;
+
+ /* 738 条记录散布在 16,384 格里 —— 每格代表 64 个真实槽位 */
+ var lit = [];
+ var rand = rng(20260914);
+ var want = Math.round(N * N * (GEO.records / 16384)); /* ≈ 738 格 */
+ var seen = {};
+ while (lit.length < want) {
+ var i = Math.floor(rand() * N * N);
+ if (seen[i]) continue;
+ seen[i] = 1;
+ lit.push(i);
+ }
+ /* 降落的终点:其中一条记录 */
+ var target = lit[Math.floor(lit.length * 0.42)];
+ var tx = (target % N) * CELL + CELL / 2;
+ var ty = Math.floor(target / N) * CELL + CELL / 2;
+
+ /* ---- 离屏烘焙整张地址空间(每秒重画 16k 个点太贵,烘一次然后只做缩放着色) ---- */
+ var off = document.createElement("canvas");
+ off.width = off.height = SPACE;
+ (function bake() {
+ var g = off.getContext("2d");
+ g.fillStyle = "#000";
+ g.fillRect(0, 0, SPACE, SPACE);
+ /* 底噪:整个地址空间的槽位骨架 */
+ g.fillStyle = "rgba(255,254,244,0.10)";
+ for (var y = 0; y < N; y++) {
+ for (var x = 0; x < N; x++) {
+ g.fillRect(x * CELL + CELL * 0.42, y * CELL + CELL * 0.42, 0.9, 0.9);
+ }
+ }
+ /* 活跃记录 */
+ g.fillStyle = "#fffef4";
+ lit.forEach(function (i) {
+ g.fillRect((i % N) * CELL + CELL * 0.34, Math.floor(i / N) * CELL + CELL * 0.34, 1.7, 1.7);
+ });
+ /* 目标记录:唯一一个用信号色标出的点,全页只在这里出现 */
+ g.fillStyle = "#00ff6a";
+ g.fillRect(tx - 1.6, ty - 1.6, 3.2, 3.2);
+ })();
+
+ /* ---- 八个环节(标签直接取自 data.js 的读取路径,单一来源) ---- */
+ var path = (D && D.readPath && D.readPath.length) ? D.readPath : [];
+ var STEPS = [
+ { label: "地址空间", value: "1,048,576 槽位 · 738 条活跃记录", zoom: 1.00 },
+ { label: path[0] ? path[0].label : "用户问题", value: "自然语言,不接触全量记忆", zoom: 0.62 },
+ { label: path[1] ? path[1].label : "隐状态地址", value: "128 维紧凑查询地址", zoom: 0.38 },
+ { label: path[2] ? path[2].label : "LSH 粗索引", value: "730 个桶,只取少量候选", zoom: 0.20 },
+ { label: path[3] ? path[3].label : "候选页", value: "24 页 × 页容量 32", zoom: 0.105 },
+ { label: path[4] ? path[4].label : "页内重排", value: "页内逐条打分", zoom: 0.052 },
+ { label: path[5] ? path[5].label : "Top-K 记录", value: "最多 2 条", zoom: 0.026 },
+ { label: path[6] ? path[6].label : "证据前缀", value: "45 token 注入模型内部", zoom: 0.014 }
+ ];
+
+ var W = 0, H = 0, dpr = 1;
+ function resize() {
+ dpr = Math.min(window.devicePixelRatio || 1, 2);
+ W = pin.clientWidth;
+ H = pin.clientHeight;
+ cv.width = Math.round(W * dpr);
+ cv.height = Math.round(H * dpr);
+ cv.style.width = W + "px";
+ cv.style.height = H + "px";
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+ ctx.imageSmoothingEnabled = true;
+ }
+
+ /* ---- t ∈ [0,1] → 画一帧 ---- */
+ function draw(t) {
+ if (!W || !H) return;
+ ctx.clearRect(0, 0, W, H);
+ ctx.fillStyle = "#000";
+ ctx.fillRect(0, 0, W, H);
+
+ /* 镜头窗口:从整张空间缩到 32×32(约五个数量级) */
+ var i = Math.min(STEPS.length - 1, Math.max(0, t * STEPS.length));
+ var a = STEPS[Math.floor(i)];
+ var b = STEPS[Math.min(STEPS.length - 1, Math.floor(i) + 1)];
+ var f = i - Math.floor(i);
+ var e = f * f * (3 - 2 * f); /* smoothstep,镜头不做生硬跳变 */
+ var zoom = a.zoom + (b.zoom - a.zoom) * e;
+
+ /* 保持长宽比并保证源矩形始终落在图内 */
+ var sw, sh;
+ if (W >= H) { sw = SPACE * zoom; sh = sw * (H / W); }
+ else { sh = SPACE * zoom; sw = sh * (W / H); }
+ var cx = (1 - e) * (SPACE / 2) + e * tx;
+ var cy = (1 - e) * (SPACE / 2) + e * ty;
+ var sx = Math.max(0, Math.min(SPACE - sw, cx - sw / 2));
+ var sy = Math.max(0, Math.min(SPACE - sh, cy - sh / 2));
+
+ ctx.globalAlpha = 1;
+ ctx.drawImage(off, sx, sy, sw, sh, 0, 0, W, H);
+
+ /* 下降越深,越暗:让叠加的字有安静的地面(page-design §3) */
+ var dim = Math.min(0.84, 0.30 + t * 0.62);
+ ctx.fillStyle = "rgba(0,0,0," + dim.toFixed(3) + ")";
+ ctx.fillRect(0, 0, W, H);
+
+ /* 越深,槽位骨架的网格线越显形 —— 空间「resolved」成结构 */
+ if (t > 0.30) {
+ var ga = Math.min(0.22, (t - 0.30) * 0.5);
+ ctx.strokeStyle = "rgba(255,254,244," + ga.toFixed(3) + ")";
+ ctx.lineWidth = 1;
+ var step = Math.max(18, 46 / (0.4 + t));
+ var ox = ((tx - sx) / sw) * W, oy = ((ty - sy) / sh) * H;
+ ctx.beginPath();
+ for (var x = ox % step; x < W; x += step) { ctx.moveTo(Math.round(x) + .5, 0); ctx.lineTo(Math.round(x) + .5, H); }
+ for (var y = oy % step; y < H; y += step) { ctx.moveTo(0, Math.round(y) + .5); ctx.lineTo(W, Math.round(y) + .5); }
+ ctx.stroke();
+ }
+
+ /* 终点:目标记录被圈出来(信号色,全页唯一一次) */
+ if (t > 0.62) {
+ var pa = Math.min(1, (t - 0.62) / 0.18);
+ var px = ((tx - sx) / sw) * W, py = ((ty - sy) / sh) * H;
+ var r = 10 + 26 * (1 - pa) + 8 * Math.sin(performance.now() / 620);
+ ctx.strokeStyle = "rgba(0,255,106," + (0.90 * pa).toFixed(3) + ")";
+ ctx.lineWidth = 1.25;
+ ctx.beginPath(); ctx.arc(px, py, r, 0, Math.PI * 2); ctx.stroke();
+ ctx.beginPath(); ctx.arc(px, py, r * 0.34, 0, Math.PI * 2); ctx.stroke();
+ }
+ }
+
+ /* ---- HUD:把当前环节读出来(同时是这台机器的探针面) ---- */
+ var hudIdx = document.getElementById("hudIdx");
+ var hudName = document.getElementById("hudName");
+ var hudVal = document.getElementById("hudVal");
+ var hudBar = document.getElementById("hudBar");
+ var lastStep = -1;
+ function hud(t) {
+ var s = Math.min(STEPS.length - 1, Math.floor(t * STEPS.length));
+ if (s === lastStep) return;
+ lastStep = s;
+ var st = STEPS[s];
+ if (hudIdx) hudIdx.textContent = String(s + 1).padStart(2, "0") + " / " + String(STEPS.length).padStart(2, "0");
+ if (hudName) hudName.textContent = st.label;
+ if (hudVal) hudVal.textContent = st.value;
+ }
+
+ /* ---- 一个循环:只在首屏可见时跑,不可见就停(省电,也便于量 FPS) ---- */
+ var t = 0, raf = 0, visible = true, lastFrame = 0;
+ function progress() {
+ var r = host.getBoundingClientRect();
+ var span = host.offsetHeight - pin.offsetHeight;
+ if (span <= 0) return 0;
+ return Math.max(0, Math.min(1, -r.top / span));
+ }
+ function step() {
+ var want = progress();
+ t += (want - t) * 0.12; /* 指数趋近:帧率无关的近似 */
+ if (Math.abs(want - t) < 0.0004) t = want;
+ draw(t);
+ hud(t);
+ if (hudBar) hudBar.style.width = (t * 100).toFixed(1) + "%";
+ lastFrame = (window.performance || Date).now();
+ }
+ function loop() { raf = 0; step(); if (visible) raf = requestAnimationFrame(loop); }
+ /* rAF 会被节流或冻结(后台标签页、无头虚拟时间、低端 WebView)。
+ 只有句柄没有帧会让循环永久卡住,所以超时就同步走一帧 ——
+ 滚动事件本身就能把画面推着走。 */
+ function kick() {
+ if (!visible) return;
+ var now = (window.performance || Date).now();
+ if (raf && now - lastFrame < 140) return;
+ step();
+ if (!raf) raf = requestAnimationFrame(loop);
+ }
+
+ /* ---- 静态帧:reduced-motion 与窄屏都走这里 ---- */
+ function still() {
+ document.documentElement.classList.add("space--still");
+ resize();
+ var frozen = narrow ? 0.30 : 0.52;
+ draw(frozen);
+ hud(frozen);
+ }
+
+ /* ---- 探针面(verification-harness §1):能被 seek / hold,才好量 ---- */
+ window.__space = {
+ seek: function (v) { t = Math.max(0, Math.min(1, v)); draw(t); hud(t); },
+ hold: function () { if (raf) { cancelAnimationFrame(raf); raf = 0; } },
+ progress: progress,
+ steps: STEPS.length,
+ geo: GEO,
+ t: function () { return t; }
+ };
+
+ if (reduce || narrow) {
+ still();
+ window.addEventListener("resize", function () { resize(); draw(narrow ? 0.30 : 0.52); });
+ } else {
+ resize();
+ if ("IntersectionObserver" in window) {
+ new IntersectionObserver(function (es) {
+ visible = es[0].isIntersecting;
+ if (visible) kick(); else if (raf) { cancelAnimationFrame(raf); raf = 0; }
+ }, { rootMargin: "120px" }).observe(host);
+ }
+ window.addEventListener("scroll", kick, { passive: true });
+ window.addEventListener("resize", function () { resize(); kick(); });
+ kick(); /* 循环常转:静止时目标点的呼吸也要活着 */
+ }
+})();
diff --git a/build.py b/build.py
new file mode 100644
index 0000000..bb51c13
--- /dev/null
+++ b/build.py
@@ -0,0 +1,324 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Natural Memory v2 — 站点构建脚本
+
+两种版式:
+ release —— 发布页(总览):无侧栏、全宽分段,按研究/模型发布页的通行骨架组织
+ docs —— 文档页(其余 9 页):左侧栏 + 窄栏正文
+
+把 `src/pages/*.body.html` 的正文片段套进 `templates/base.html` 骨架;
+正文里可用 `{{include:partials/xxx.html}}` 引入 `src/partials/` 下的共用片段。
+
+用法:
+ python build.py # 生成全部页面
+ python build.py --check # 只校验,不写文件
+"""
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+import re
+import sys
+from datetime import datetime
+
+ROOT = os.path.dirname(os.path.abspath(__file__))
+SRC = os.path.join(ROOT, "src", "pages")
+PARTIALS = os.path.join(ROOT, "src", "partials")
+TPL = os.path.join(ROOT, "templates", "base.html")
+
+# slug, 序号, 中文标题, 英文小标, 分组, , 摘要
+PAGES = [
+ ("index", "00", "总览", "OVERVIEW", "概览",
+ "Natural Memory v2 — 模型内的记忆层",
+ "接在本地 Qwen3.5-4B 上的模型内记忆层。模型重启后不携带历史聊天记录,仍能访问已保存的个人与项目事实,且不把全部历史转换成 GPU 上的长 KV Cache。"),
+ ("division", "01", "记忆与 KV 的分工", "DIVISION OF LABOUR", "原理",
+ "记忆与 KV 的分工 — Natural Memory v2",
+ "为什么不是简单加长上下文:哪些东西该留在 Qwen 的热 KV 里,哪些该写入带地址的记忆记录,各自的边界在哪里。"),
+ ("mechanism", "02", "一次读取与一次写入", "MECHANISM", "原理",
+ "机制:读取路径、记录结构与写入状态机 — Natural Memory v2",
+ "从用户问题到证据前缀注入的八个环节,记录包含哪些字段,以及 active / superseded / quarantined / retracted 四个状态如何演化。"),
+ ("results", "03", "测量结果", "MEASUREMENTS", "证据",
+ "测量结果 — Natural Memory v2",
+ "同一 264 题题集的两个独立批次、结构化工程基准与记忆路由器工程线的全部实测数字,均标注出处与口径。"),
+ ("nm21", "04", "NM2.1 · 之后做了什么", "WHAT CHANGED NEXT", "证据",
+ "NM2.1:之后做了什么 — Natural Memory v2",
+ "三处改动各自的测量依据、把端到端正确率锁死在 50% 的写入缺陷如何定位、75% 未知泄漏如何修到 0%,以及它接上运行时连续失败两次的完整记录。"),
+ ("examples", "05", "实测样例", "GROUND TRUTH", "证据",
+ "实测样例 — Natural Memory v2",
+ "五条逐字取自评测原始结果的问答对照,包含两条我们判错的样例。"),
+ ("ledger", "06", "真实评测台账", "FULL LEDGER", "证据",
+ "真实评测台账 — Natural Memory v2",
+ "全部 108 条真实评测 episode 的逐条台账:模型逐字回复、判定结果、停止原因、选中记录数与分数,可按来源与判定筛选。"),
+ ("cost", "07", "代价账本", "COST", "工程",
+ "代价账本 — Natural Memory v2",
+ "记忆带来的延迟、解码速度、前缀长度与显存开销,以及显存快照的口径说明。"),
+ ("capacity", "08", "容量与存储", "CAPACITY & STORAGE", "工程",
+ "容量与存储 — Natural Memory v2",
+ "地址空间、分页结构、有界 GPU cache 与嵌入式 weight-shard 持久化的实际数字与取舍。"),
+ ("limits", "09", "我们不宣称的内容", "BOUNDARIES", "工程",
+ "边界:我们不宣称的内容 — Natural Memory v2",
+ "没有足够证据支持的八条说法、五项主要局限,以及来自工程文档的原文引用。"),
+ ("roadmap", "10", "已解决的与下一步", "STATUS & NEXT", "工程",
+ "已解决的与下一步 — Natural Memory v2",
+ "在 12 GiB GPU 上已经跑通的闭环,以及按对正确率最有帮助的顺序排出的下一阶段任务。"),
+ ("reproduce", "11", "复现实验", "REPRODUCE", "工程",
+ "复现实验 — Natural Memory v2",
+ "正式脏数据迁移测试的完整命令、协议参数与产物路径。"),
+]
+
+AUTHOR_SITE = "https://wpyw.site"
+AUTHOR_MAIL = "wpy@wpy.email"
+
+# 每页的头号数字。文档页的页眉由一个**真实数字**领起,而不是只有标题:
+# 这些页面本来就是「证据页」,让它自己先把最强的那条数字说出来。
+# 顺带承担移动端的字阶落差 —— 中文标题在 320px 上推不到 7×,数字可以。
+KEYS = {
+ "division": ("441.19", "tok", "批次 B 平均输入前缀 token"),
+ "mechanism": ("1,048,576", "", "地址空间设计容量"),
+ "results": ("82.52", "%", "批次 B 可回答正确率 · 批次 A 为 62.60%,两批配置不同、不可合并"),
+ "nm21": ("94.14", "%", "路由器 Top-1(v2 为 41.12%)"),
+ "examples": ("5", "", "逐字原文,其中 2 条是我们主动放进去的判错样例"),
+ "ledger": ("108", "", "条真实评测 episode,含全部失败"),
+ "cost": ("26.35", "ms", "读取路径额外耗时(批次 B;批次 A 为 61.10 ms)"),
+ "capacity": ("512", "B", "每条记录地址字节 · 100 万条约 512 MB"),
+ "limits": ("75.00", "%", "跨域未知泄漏率 —— 仍未解决,覆盖门已回退"),
+ "roadmap": ("52", "", "项单元测试通过"),
+ "reproduce": ("264", "", "题题集 · 246 可回答 / 18 未知"),
+}
+
+GROUP_ORDER = ["概览", "原理", "证据", "工程"]
+RELEASE_SLUG = "index" # 这一页用发布页版式,其余用文档版式
+
+
+def asset_version() -> str:
+ """assets 静态资源的短指纹:内容一变 URL 就变,浏览器不会再用到旧资源。"""
+ h = hashlib.md5()
+ files = []
+ for sub in ("css", "js"):
+ d = os.path.join(ROOT, "assets", sub)
+ if os.path.isdir(d):
+ files += [os.path.join(d, f) for f in sorted(os.listdir(d)) if f.endswith((".css", ".js"))]
+ for p in files:
+ with open(p, "rb") as fh:
+ h.update(fh.read())
+ return h.hexdigest()[:10]
+
+
+def expand_includes(text: str) -> str:
+ """把 {{include:partials/x.svg}} 展开成 src/partials/x.svg 的内容。"""
+ def repl(m):
+ rel = m.group(1).strip().lstrip("/")
+ p = os.path.join(ROOT, "src", rel)
+ if not os.path.isfile(p):
+ sys.stderr.write("[警告] 找不到 include: %s\n" % rel)
+ return "" % rel
+ with open(p, "r", encoding="utf-8") as fh:
+ return fh.read().rstrip()
+ return re.sub(r"\{\{include:([^}]+)\}\}", repl, text)
+
+
+def href_for(slug: str) -> str:
+ return "./" if slug == RELEASE_SLUG else "./%s" % slug
+
+
+def render_sidebar(active: str) -> str:
+ out = ['← 返回发布页']
+ for group in GROUP_ORDER:
+ items = [p for p in PAGES if p[4] == group and p[0] != RELEASE_SLUG]
+ if not items:
+ continue
+ out.append('%s
' % group)
+ out.append('')
+ for slug, num, zh, en, _g, _t, _d in items:
+ cls = "side__a is-on" if slug == active else "side__a"
+ cur = ' aria-current="page"' if slug == active else ""
+ out.append('- %s'
+ '%s
' % (cls, href_for(slug), cur, num, zh))
+ out.append("
")
+ return "\n".join(out)
+
+
+def render_prevnext(active: str) -> str:
+ """上一节 / 下一节:只在文档页出现,且把发布页作为首尾的邻页。"""
+ if active == RELEASE_SLUG:
+ return ""
+ order = [p[0] for p in PAGES]
+ idx = order.index(active)
+ prev_p = PAGES[idx - 1] if idx > 0 else None
+ next_p = PAGES[idx + 1] if idx < len(PAGES) - 1 else None
+
+ def link(p, direction):
+ if not p:
+ return ''
+ slug, num, zh = p[0], p[1], p[2]
+ label = "上一节" if direction == "prev" else "下一节"
+ kbd = "←" if direction == "prev" else "→"
+ return (''
+ '%s · %s%s'
+ '%s' % (direction, href_for(slug), direction, label, num, zh, kbd))
+
+ return '' % (link(prev_p, "prev"), link(next_p, "next"))
+
+
+def render_topnav(active: str) -> str:
+ """顶栏快速导航:发布页与文档页共用,当前页高亮。"""
+ items = [
+ ("index", "发布页"),
+ ("mechanism", "机制"),
+ ("results", "测量"),
+ ("nm21", "NM2.1"),
+ ("ledger", "台账"),
+ ("limits", "边界"),
+ ("reproduce", "复现"),
+ ]
+ out = []
+ for slug, label in items:
+ cls = "topnav__a is-on" if slug == active else "topnav__a"
+ cur = ' aria-current="page"' if slug == active else ""
+ out.append('%s' % (cls, href_for(slug), cur, label))
+ return " ".join(out)
+
+
+def render_main(slug: str, num: str, zh: str, en: str, desc: str, content: str) -> str:
+ if slug == RELEASE_SLUG:
+ # 发布页:无侧栏,全宽分段
+ return ('\n%s\n ' % content)
+ fig = ""
+ if slug in KEYS:
+ k, unit, note = KEYS[slug]
+ fig = (' \n'
+ ' %s%s\n'
+ ' %s\n'
+ '
\n' % (k, unit, note))
+ return (
+ '\n'
+ ' \n'
+ ' \n'
+ ' \n'
+ ' %s / %s\n'
+ ' %s
\n'
+ ' %s
\n%s'
+ ' \n%s\n%s\n \n'
+ '' % (render_sidebar(slug), num, en, zh, desc, fig, content, render_prevnext(slug))
+ )
+
+
+_SVG_RE = re.compile(r"