Files
natural-memory-site/assets/js/space.js
T

257 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ==================================================================
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(); /* 循环常转:静止时目标点的呼吸也要活着 */
}
})();