海边

2026-10-06

维护记录

完成的工作

  • 标定左下角粒子徽标的密度 / 清晰度上限,并按实测结果调参 $cfg ✅
    • 改动文件:themes/sdttttt-paper/layouts/partials/bg.html($cfg 数值 + 上方新增实测标定注释)、themes/sdttttt-paper/assets/js/page-bg.js(GRAIN 的说明注释,逻辑未动)
    • 参数变化:pitchCss 2 → 1.2、maxParticles 60000 → 200000、sizeRatio 0.85 → 0.95、grain true → false、dprCap 1.5 → 2
    • 效果:step 5 → 3(每 3 个源像素采一颗),粒子数 19,849 → 55,103(×2.8),覆盖率 24.6% → 38.4%
    • 只改 $cfg,没有重跑 deno task build-wasm —— 所有参数都是运行时传进 wasm 的,themes/sdttttt-paper/assets/wasm/particles.wasm 不需要重建
  • 测试台(/tmp/pt-lab/,跑完已删):用 Deno 直接驱动 particles.wasm(alloc → 写 rgba → build → settle,读 fb_ptr 存 PNG),跑了 1344 组参数矩阵 × 3 张 bg 图 × 5 种视口宽度 × 3 种 dpr,输出 particle_count / sample_step / particle_size / 覆盖率 / wasm 内存 / build 与 settle 耗时

实测出的三条机制(都验证过)

  1. step = round(pitchCss × 源图宽 / 画布CSS宽),与 dpr 完全无关 —— dpr 在 scale 与 pitchDev 里同时出现被约掉了。pitchCss 2 在 800px 源图 / 320px 画布下就是 round(2 × 800/320) = 5。pitchCss 实际被量化成阶梯:0.5–0.9→step2、1.0–1.3→step3、1.5–1.7→step4、1.8–2.2→step5
  2. dprCap 不参与密度计算,只决定 sizePx 的取整粒度与光栅锐度:size_css ≈ step × 0.4 × ratio,dpr 只影响它被 floor / round 到哪个整数。所以把 dprCap 从 1.5 提到 2(其余不动)时 step 和粒子数完全不变,只是 size 从 2 变 3 → 覆盖率 24.6% → 31.2%
  3. 真正的密度硬上限在 Rust 侧:wasm/particles/src/lib.rs 里 let cap = max_particles.clamp(200, 400_000)。首图 F_tZfZBbwAALItU.png 在 step1 需要 496,142 个不透明源像素 > 400,000,所以最少只能到 step 2。用 pitchCss 0.2 配 maxParticles 400k / 500k / 900k 各跑一遍都仍然卡在 step2 / 124,008 粒子

关键实测数据(单进程独立跑,避免 wasm 堆高水位污染)

配置stepsizePx粒子数覆盖率wasm 内存buildsettle
改前(pitch2/r.85/dpr1.5)52 (1.33css)19,84924.6%9.88MB1.3ms0.5ms
仅 dprCap→253 (1.50css)19,84931.2%10.88MB1.3ms0.6ms
改后(本次采用)32 (1.00css)55,10338.4%12.13MB1.9ms1.2ms
密度极限(pitch0.9)22 (1.00css)124,00855.7%14.31MB2.8ms2.5ms
  • 换图 / 换视口的稳定性(改后配置,dpr 2 下 170–320px 全宽度 size 恒为 1.00 CSS px):
    • F_tZfZBbwAALItU.png step3 / 55,103 粒子 / 覆盖率 38.4%
    • FxC7q1YaUAE8SOL.png step3 / 35,891 粒子 / 覆盖率 25.0%
    • GiG69PDa8AAvoeS.png step3 / 25,666 粒子 / 覆盖率 33.9%
  • 触发浏览器里的真实参数(hugo --minify 产物 public/index.html 里 #pt-bg-config)已核对为 {"dprCap":2,"fitH":1,"fitW":1,"grain":false,"maxParticles":2e5,"pitchCss":1.2,"sizeRatio":0.95}
  • 验证:hugo --minify 通过(265 页)、deno task test 通过(28/28)

遇到的问题

  • grain: true(原来的值)在低 dpr 下会自己把画面搞虚 ⚠️ - 源码里 grain 为真走 floor,会把 size 压到 1 设备像素,也就是 0.5 CSS px,浏览器把画布下采样到 CSS 尺寸后这些点会被摊薄、整块发白。实测 pitchCss 0.9 + ratio 0.85 + grain true → size 1(0.50css)、覆盖率只有 21.6%;同参数换成 ratio 0.95 + grain false(走 round)就能稳定拿到 1.00 CSS px、覆盖率 55.7%。这是本次把 grain 改成 false 的直接原因
  • 原配置在窄视口会塌方 ⚠️ - ratio 0.85 + grain true 在 cssW 220px 时 size 掉到 1 设备像素(0.50css),覆盖率只剩 11.4%。改成 0.95 + round 后 170–320px 全宽度都守住 1.00 CSS px
  • dprCap 不是越大越好 ⚠️ - dpr3 下同样是 step2,但 size 仍被 round 到 2 → 0.67 CSS px,覆盖率反而从 55.7% 掉回 38.4%。2 才是最优点,所以 dprCap 定在 2
  • 另外两张 bg 图其实能到 step1 ⚠️ - FxC7q1YaUAE8SOL.png 323,022 / GiG69PDa8AAvoeS.png 230,964 个不透明像素都没超过 400,000 硬上限,step1 可达;但那时 size 只能给到 1 设备像素(0.50css),视觉上反而比 step2 更虚,所以没有为了这两张图去改 Rust 里的 clamp(200, 400_000)
  • 测试台里 sharp 返回的 Buffer 必须显式拷贝(new Uint8Array(raw.byteLength) 再 set(new Uint8Array(raw.buffer, raw.byteOffset, raw.byteLength))),直接把 raw.buffer 交给 wasm 会报 RuntimeError: memory access out of bounds ⚠️ - sharp 用的是池化内存(byteOffset != 0),必须按 offset/length 重新拷一份

下次建议

  • 想再往上加密度只剩「改 Rust 硬上限 + 重建 wasm」一条路,而且 step1 的 size 会掉到 0.5 CSS px,性价比很低;当前 step3 已经是「保留颗粒缝隙」前提下的实用最优
  • 粒子密度旋钮以后只动 pitchCss(阶梯量化成 step),清晰度只动 dprCap(上限 2),别指望 maxParticles 能改密度 —— 它只是 step 搜索的上限
  • 若要给不同视口都锁定同一个 step,可以在 page-bg.js 里把 pitch 改成视口相对量(pitchCss = step × cssW / iw),现在是固定的 CSS px,所以 step 会随宽度在 3–6 之间漂
  • 本次只改了 $cfg 与注释,没有 commit / push;下次若要上线记得写进 commit message(建议 feat(bg): 提高徽标粒子密度与清晰度)

维护记录(续)

完成的工作

  • 按用户要求把粒子数压到 3w–4w 量级:maxParticles 200000 → 40000 ✅
    • 只动了 themes/sdttttt-paper/layouts/partials/bg.html 的一个数字 + 对应的注释(pitchCss / sizeRatio / grain / dprCap 都不变)
    • 机制:maxParticles 是粒子预算 —— Rust 侧 count_opaque(step) > 预算 就把 step 往上推一位。首图 step3 是 55,103 > 40,000,所以被推到 step4 = 31,025 粒子
    • 实测(首图 F_tZfZBbwAALItU.png,dpr2 / cssW 320 / 单进程独立跑):
maxParticlesstepsizePx粒子数覆盖率buildsettlewasm 内存
200000(改前)32 (1.00css)55,10338.4%1.50ms1.26ms12.13MB
40000(改后)43 (1.50css)31,02548.7%1.25ms0.91ms11.00MB
  • 换图后粒子数(dpr2 / cssW 320):F_tZfZBbwAALItU 31,025 / FxC7q1YaUAE8SOL 35,891 / GiG69PDa8AAvoeS 25,666,都落在 3w–4w 量级 ✅
  • 验证:hugo --minify 通过、deno task test 通过(28/28)、产物 #pt-bg-config 里 "maxParticles":4e4 已核对
  • 对比图(源图 / 55k / 31k)出在 /tmp/pt-lab2/out/cmp-zoom.png,看完随测试台一起删

遇到的问题

  • maxParticles 不能设太小 ⚠️ - 设成 30000 时 step 会从 3 直接跳到 5(step4 是 31,025 > 30,000),粒子数反而掉到 19,849、size 涨到 2.00 CSS px,比 40000 更差。安全下限是 31,025(也就是刚好容下 step4),取 40000 留一点余量
  • 压粒子数必然让粒子变大 ⚠️ - size 是 step × scale × sizeRatio,step 从 3 涨到 4 就意味着网格变粗、边长从 1.00 涨到 1.50 CSS px;想「粒子变少但边长不变」只能把 sizeRatio 降到 0.6,实测覆盖率会掉到 21.6%、画面明显发虚,所以没走这条路
  • 收益其实很小(1.1MB 内存、build 1.5ms → 1.25ms),视觉损失却很明显(细网格 → 粗块状)⚠️ - 如果之后觉得画面变糙了,把 maxParticles 改回 100000 以上就能回到 step3 / 55,103 粒子的细网格

下次建议

  • maxParticles 现在是「粒子预算」而不是「保险丝」,改它会直接改视觉密度:< 31,025 会跳到 step5(更粗),≥ 55,103 才回到 step3(更细);中间是 step4 的死区
  • 配套的可视化测试台已在 /tmp/pt-lab2/(sweep2.ts 驱动 wasm、montage2.ts 拼对比图),需要时可以直接重建