/* ================================================================== 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(); /* 循环常转:静止时目标点的呼吸也要活着 */ } })();