项目印记

预测未来的最好办法,就是把它创造出来。--艾伦.凯(美)

开发手记:规则、踩坑与修复(工程向)子文章

写作标准(2026-11-11 起执行):本文及以后所有工作记录须满足三条硬性标准——①别人读得懂规则(内部名词配通用叫法+术语映射表);②说清设置原因(问题现象→根因→通用规则);③可复制到其他程序(附可移植代码段+参数单位)。完整要求见项目文档 docs/工作文档写作要求.md。

本文记录《银河系 · 人类认知版图》模块(/galaxy/)的开发规则、踩坑与修复全过程,供后续维护者参考。配套互动页面:银河系 · 人类认知版图(轻量版 /galaxy/classic/)。

一、开发规则(约定俗成)

规则内容
版本标识每次修改 galaxy.html 必须 bump 面板底部 build 号(vYYYYMMDD-NN),用户凭此确认自己不在看缓存旧版
缓存策略页面响应设 no_store;用户侧任何异常先 Cmd+Shift+R 强刷排除缓存(Safari 高发)
数据分级页面上每种数据必须标注性质:实测(Gaia/档案)/ 实时(TLE+SGP4)/ 统计模型,不混淆
抓取容错数据管道失败保留旧缓存,原子写入(.tmp+rename),前端 fetch 失败静默回退内置数据,页面永不空白
双副本file:// 独立副本 ~/trading-dashboard/milky-way.html,每次改完手动同步 cp
提交纪律webmark.html(用户本地修改)与 backups/ 永不提交
性能看门狗真实帧间隔均值 >40ms(25fps)逐级降画质(分辨率×0.8 → 1x → 装饰亮星 → 尘埃团,共 4 级);统计必须用未钳制的 rawDt——钳制后的 dt 让看门狗永远触发不了(v33 前是死代码);渲染循环 try/catch 保底
显存纪律纹理必须缓存共享(glowTex 式);像素比三重上限 min(1.5, dpr, √(550万/(css宽×高)))——按绝对像素预算而非固定比例(Retina 全屏画布是显存/填色率最大头);行星贴图 1024² 足够,不盲目上 4K
性能排查报"卡死/卡顿"先 ps aux | grep WebKit.GPU:GPU 进程 >100% CPU 即填色率打满(与显存不足白屏是两回事);紧急处置 kill 该进程可无损复活(上下文丢失→自动 reload 链路已有)
落盘验证python 脚本批量替换后必须 grep 验证实际落盘——断言失败时后续命令仍会执行(sed bump / soak / cp 同步都是独立行),曾三次"改了个寂寞"(v89 辉光、v97 退褐层、v101 配色)

二、交互定稿

  • 左键拖动=环绕(以鼠标所点为枢轴)· 中/右键拖动=平移 · 滚轮=光标锚定缩放(I 键反转)· Safari 捏合=缩放 · Esc 取消选中
  • 自动选中:焦点贴近行星 5 倍半径内、或光标射线指向 8 倍半径内 → 自动选中该行星,以其表面(半径×1.02)为缩放地板;缩远(>1.15 倍)自动释放
  • 太阳光球地板 3.5e-4 kpc;空域行走:焦点远离一切天体且贴近焦点时,"放大"变为朝视线方向前进(每格≤2 kpc)
  • 缩放范围 2e-7–75 kpc;近裁剪面 dist×0.04(下限 1e-7),远平面跟随拉近 max(0.05, dist×2000),保证 far/near < 2^24

三、问题与解决(按时间序)

现象根因修复
Safari 双指捏合毫无反应,滚轮计数却猛涨Safari 捏合派发 deltaY=0 的 wheel 事件,真实缩放在私有 gesturestart/gesturechange 事件里新增 gesture 监听(window+document 双挂)映射到 zoomStep,preventDefault 阻止整页缩放
选中行星放大到一半行星消失旧淡出逻辑:距离<10 倍半径开始变透明行星常显(opacity=1),双面渲染防穿入黑屏
卡住不动、距离钉在 0.0041 等怪值,"只有点选行星后才能继续放大"事件快于帧率:触控板连发时,zoomStep 用上上帧的 camera.position 做锚定,每步焦点漂移,滚得越快偏越狠zoomStep 第一步 updateCamera(1) 先按 view 模型收敛相机,锚定数学不依赖外部帧同步
深推银河后页面彻底冻住(GPU 死)深度缓冲精度崩溃:近裁剪面 8e-9、远平面恒定 300,far/near≈3.7e10,超 24 位深度极限 1.6e7 两百万倍远平面自适应 max(0.05, dist×2000),far/near 恒在精度内
焦点漂到 13 kpc 空域后放大完全失效tDist 相对 13 kpc 空域无意义,缩小距离画面纹丝不动空域行走模式:远离天体且贴近焦点时,"放大"= 朝视线方向 dolly 前进
深推后右键平移无效平移速度与 view.dist 成正比,dist=1e-6 时拖 1000px 只挪 1.6e-6平移速度加下限 max(dist, 5e-5)
刷新后画面突然缩放、距离显示 0.0000诊断参数 ?wheeltest=1 残留在用户网址里,每次刷新自动跑 120 次缩放;且 toFixed(4) 把 2e-7 显示成 0.0000排障代码用完即删;距离显示改自适应(<0.001 用科学计数法)
苹果绑定卡验证等无关问题混入排查—与用户确认无关后搁置

四、调试方法论(本模块专用)

  • 面板诊断行:实时统计 按下/拖动(旋转/平移)/滚轮(距离前→后)/按键/帧数 + zoomStep 环形追踪(因子|前>后|地板|选中对象|dSun),用户截图即真相
  • headless Chrome 复现:--screenshot 截图 + --dump-dom 读注入的测试 DIV + grep Uncaught 查报错;模拟滚轮用临时 URL 参数注入
  • 合成事件陷阱:同步派发 60 个滚轮事件不等价真实交互(相机不更新)——曾被合成测试误导,需 updateCamera(1) 同步后再断言
  • TDZ 陷阱:const 声明前引用直接报错;性能代码引用 dt 必须先于声明检查

五、数据管线

刷新命令 venv39/bin/python -m app.galaxy.fetch_data:① NASA 系外行星档案 TAP(5,058 宿主星)② 策展近邻表 24 颗 ③ CelesTrak 卫星 TLE 8,047 颗(GitHub 镜像双源回退)④ Gaia DR3 近域星表:视差>6.52 mas 且 parallax_over_error>5,按 G 星等取前 20,000 颗(太阳 500 光年内),坐标服务端按 raDecDist2xyz 同公式预计算。产物 app/galaxy/data/*.json。

六、性能与显存:Safari 白屏专题(2026-11-08)

现象:Safari 中页面偶发白屏一闪后自动恢复(偶发一次)。

显存开销账本(5K Retina 屏实测估算):

项目占用说明
几何缓冲~6 MB银河盘 6 万点 + 近域星 2 万 + 小行星 5 万 + 卫星 8 千,均 Float32 attribute
纹理80–120 MB行星 1024² 贴图×9+凹凸+环 ≈ 40-50 MB(mipmap 翻倍);光晕精灵原本每精灵一份 canvas 纹理 ≈ 15-20 MB;程序纹理若干
渲染缓冲~236 MB5K 全屏 × DPR2 = 5120×2880 × 4 字节 × 前后双缓冲 + 深度缓冲,单项最大头

Safari 为什么特别敏感:Apple Silicon 显存即内存,但 WebKit 对 WebGL 上下文有主动限额——系统内存压力稍大就挑最重的上下文杀掉(表现为 context lost),Chrome 倾向保留页面。Retina 全屏画布 + 270MB 组合是触发重灾区。

两项优化(v20261108-27):

优化做法收益
光晕纹理共享glowTex() 加缓存,同色精灵共享一张 GPU 纹理(95 次上传 → 7 次)省 ~15 MB,画面零变化
渲染像素比上限 1.5setPixelRatio(min(1.5, DPR)),5K 屏渲染分辨率 5120×2880 → 3840×2160渲染缓冲 236→133 MB;填色率瓶颈(加法混合星点/光晕)帧率接近翻倍;白屏概率大降。代价仅贴脸看行星纹理略软,星点/标签无感

既有防线(保持不变):性能看门狗低帧率自动 setPixelRatio(1) 逐级降画质;webglcontextlost 监听 + 自动刷新恢复;渲染循环 try/catch 保底。

具体实现(可照抄的代码级方案)

方案实现要点
① 纹理缓存共享glowTex(rgb) 入口加 const cache={}; if(cache[rgb]) return cache[rgb];,返回前写入缓存。同色光晕(星芒/光晕/邻星)从 95 份 GPU 副本降为每种颜色 1 份。行星贴图同理可挂 THREE.TextureLoader 的缓存
② 渲染像素比三重上限(v33 升级)建 renderer 时 PR_CAP = min(1.5, devicePixelRatio, √(5.5e6/(css宽×css高)))——除 1.5/dpr 外再加绝对像素预算,5K 屏缓冲 ≤550 万像素。看门狗降级 setPixelRatio(1) 仍生效,两级互不冲突
③ 性能看门狗(v33 才真活)每 240 帧统计未钳制 rawDt 均值,>40ms(25fps)逐级降 4 级:分辨率×0.8 → 1x → 隐藏十字亮星 → 隐藏尘埃团。v33 之前是死代码:dt 先被 Math.min(0.05) 钳制,均值永远够不到 66ms 阈值(见第七节)。累积计时器防抖动
④ 裁剪面自适应(已有)near = clamp(dist×0.04, 1e-7, 0.1),far = clamp(dist×2000, 0.05, 300),保证 far/near < 2^24(24 位深度精度极限),深推时远平面自动拉近省深度带宽
⑤ 离屏不更新(已有)小行星带:相机距太阳 >0.6 kpc 时暂停 5 万颗开普勒求解;卫星同理按可视距离分块传播(自适应 chunk:>30ms 砍半,<6ms 回升)
⑥ 几何全用 BufferGeometry + 共享材质(已有)银河盘 200 万点单 drawcall(v36 升级,总光度守恒:点数×33 单点亮度÷4);星点着色器 clamp(gl_PointSize, 1, 10) 防极端放大撑爆填色率;加法混合 depthWrite:false 避免透明排序开销
⑦ 上下文丢失恢复(已有)canvas 监听 webglcontextlost(preventDefault)→ 提示「正在自动恢复」;webglcontextrestored → location.reload()。白屏一闪即恢复的关键

续发与定案:连续白屏的真凶是 CDN(v20261108-31)。当晚"偶发"恶化为"连续白屏"——每次打开都是全白。Playwright WebKit 复现:页面 HTML 已返回,但 <head> 里两个 CDN 脚本(jsdelivr 的 three.js / satellite.js)请求挂死无响应,HTML 解析器阻塞在脚本上,DOMContentLoaded 永远不来,body 都是 null。Chrome/curl 访问同一 CDN 却正常(1 秒返回)——WebKit 网络栈下 CDN 被掐,故障与页面代码无关。修复:两个 JS 库 + 16 张行星贴图(含月球,共 5.5MB)全部本地化到 /static/,页面零外部依赖,断网可开。教训:① 关键脚本绝不放阻塞式外链 CDN;② 全白(含 UI)先怀疑"解析期阻塞",部分白(canvas 黑、面板在)才怀疑渲染期。

还能做的:行星贴图分级加载(远景程序纹理、近景才拉 1024² 真贴图,省 ~40MB 常驻);星座光晕精灵合并为 Points。

七、Safari 卡死专题:GPU 填色率打满 + 看门狗死代码(2026-11-08 v33)

现象:Safari 中银河页整浏览器卡死(非白屏)。排查:服务端全部端点 curl 正常(<0.4s),headless Chrome/WebKit 渲染正常——ps aux | grep WebKit.GPU 现形:GPU 进程 140% CPU、WebContent 63%、1.5GB 内存,从页面加载起持续打满。

根因(三个叠加):

#根因说明
15K 屏填色率过载渲染缓冲 830 万像素 × 加法混合(星点+光晕+大透明精灵多层全屏过度绘制)。Playwright WebKit 不复现只因测试视口 1600×1000(160 万像素),5K 是它的 5 倍——性能 bug 必须在用户真实分辨率下复现
2看门狗是死代码帧间隔先经 Math.min(0.05, dt) 钳制再统计,均值永远 ≤50ms,而触发阈值是 66ms——条件永真不成立,看门狗自写上起从未生效。教训:统计量必须用未钳制的原始值
3阈值过松15fps 才触发;GPU 打满时帧率通常还有 20–30fps,永远不触发。调到 25fps

修复(v20261108-33):

  • 看门狗改用未钳制的 rawDt,阈值 40ms(25fps),阶梯扩为 4 级:渲染分辨率 ×0.8 → 1x → 隐藏装饰亮星 → 隐藏尘埃团,刷新恢复满画质
  • 初始像素比加绝对像素预算:PR_CAP = min(1.5, dpr, √(5.5e6/(css宽×css高))),5K 屏渲染缓冲从 830 万像素降到 ≤550 万(-34%,等效 fill-rate 同比例下降)——降压发生在加载时而非卡死后
  • 紧急处置手法:kill 卡死的 WebKit.GPU 进程(自动重建,页面经 webglcontextlost→reload 自动恢复),比退出 Safari 快且无损

处置与验证记录:kill 卡死 GPU 进程后 Safari 自动重建、标签页经上下文丢失→reload 链路自动恢复,重建后 GPU CPU 归零;修复后 Playwright WebKit 浸泡测试(模拟滚轮深推)+ Chrome headless 回归均零报错,file:// 备份副本同步更新(build v20261108-33)。

与"白屏专题"(第六节)的区分:白屏=渲染缓冲/CDN 等加载期故障(资源拿不到或上下文建不起来);卡死=运行期填色率/GPU 饱和。白屏看网络与显存,卡死看 GPU 进程 CPU——排查入口不同。

八、渲染美学定稿记(2026-11-08~09,v97 → ac9962f)

最终配方:蓝紫烟雾荧光基调;盘 200 万点单 drawcall(每点≈10 万颗真实恒星,单点亮度 0.22,点数×33 亮度÷4 的总光度守恒);臂形自中心棒端(R≈2.2)出发、内区淡出;银晕 r^-3.5 聚集;核球 4.8 万点 4px 像素上限+流量守恒着色器(点被钳制时按面积比降 alpha,贴脸不糊白 sheet);星座天球太阳中心、半径 2.4 kpc、星名两级显示。

走过的弯路(都退掉了):NASA 银蓝金配色系列(v34–39)、三色分层星沙(v91–96)、bloom 辉光后处理(v89,r128 渲染目标视口会二次乘 pixelRatio、加法累积需预乘 alpha 混合——两个深坑记录在案)、盘星密度调节按钮(v87)。教训:配色类偏好一旦定调(蓝紫烟雾),参考图驱动的改色是无限回归,先冻结再迭代。

性能悬案一桩:v91 后帧率 60→6 fps,逐层隐藏排查无效,最终定位白层数组多分配 45 万零点全堆银心——数组类 bug 必须验证填充数与长度,不能只查代码逻辑;且当时 macOS signpost_reporter 守护进程空转(负载 2.7)放大了所有测量噪声,排查前先确认机器基线。

九、知识化改造与交互迭代:规则与实施方法(2026-11-10)

按 /artcles/251 路线图的 ①信息卡→③十站导览→②位置感知知识→④星座故事→⑤KB 打通 落地,外加一整天的高密度交互迭代。本节记录当天立下的规则与可复用的实施方法。

9.1 当天立下的规则

规则内容
功能入口唯一化同功能只留一个入口。飞往太阳系/银河全景/导览/行星标签/恒星标签/音乐六个功能从面板收拢到底框图标行,面板同名按钮删除,避免两处状态同步成本
合并信息条同屏悬浮框只留一个:导览横幅与位置感知知识合并为底部固定框,导览态与知识态互斥(导览中暂停知识分档轮询),结束后 kbTierCur=-1 强制重渲染接管
悬停即讲述三件套悬停出卡必须:换目标切内容(同一帧)/ 离开 15 秒自动关闭 / 超时只关"悬停弹出的卡"(比对 cardState 引用,不误关行星卡)。悬停命中按可见标签元素的真实文字带判定,不按锚点——placeLabel 用 translate(-50%,-130%) 定位,文字带在锚点上方 5–27px,按锚点 ±16px 判定会漏掉文字上半截("悬停金星无响应"案)
可拖动悬浮框拖动必须四件套:实时钳制不可出屏(拖动中就要钳,不能等 mouseup)/ 松手吸附(左/右/上沿 110px 阈值)/ localStorage 记忆 / 窗口缩放时拉回视口
全屏演示生命周期导览开始 requestFullscreen(必须在真实用户手势里调用)、结束 exitFullscreen、监听 fullscreenchange——用户按浏览器 Esc 退全屏时同步结束导览(flag 标记全屏是否本导览发起)
搜索三件套任何搜索直达必须"显现目标 + 导航到位 + 弹出信息卡"齐全。只设相机焦点不设缩放距离 = 目标在 20 kpc 外是亚像素 speck("搜索月球不能定位"案);行星/月球搜索同步 view.tDist = 显示半径×5(同导览站参数)
行星贴图五规矩①真实照片优先(NASA/专业拼接图);②慎用凹凸贴图——三.js bump 是屏幕空间导数,近距放大必出满屏噪点(月球案),浮雕感靠照片本身;③贴图两极劣质填充带用 UV offset/repeat 裁剪(月球上下各裁 12%);④贴图自转必须绕南极轴心 center=(0.5,0),绕角落旋转会把均匀纹理甩成"白斑补丁"(天王星案);⑤所有贴图开 anisotropy=renderer 最大值,斜视角不糊
搜索即显现(88 星座)数据全量就位但默认 hidden(visible=false 不占渲染不响应交互),搜索显现后常驻并 localStorage 记忆;显现走"先建后藏"(reveal 时统一 visible=true 并登记进淡出管理列表),名字/悬停/双击全部按 hidden 标志短路
验证用真实事件requestFullscreen 等需要 transient activation 的 API,JS .click() 合成事件不触发用户激活,selenium 必须用 ActionChains 真实鼠标;合成事件测试注意字符串替换别误伤 clientX 里的 'X' 占位符

9.2 可复用的实施方法

方法说明
逐层隔离排查法"火星是纯色的"排查链:换已知好贴图(仍纯色)→ 换无光照 BasicMaterial(仍纯色)→ 新球体+新贴图对照(正常)→ 查 UV 属性(正常)→ 打印 map.matrix —— 全 NaN。结论:纹理对象生成后某个环节把变换矩阵写成了 NaN,顺藤摸瓜发现行星对象缺 phase 属性导致 map.rotation=NaN。教训:three.js 渲染异常先打印 material.map.matrix.elements,NaN 会静默渲染成"可用纯色",不报错。此案是当天最大收获——九大行星+太阳的真实照片贴图自某次重构后从未生效,纯靠程序色兜底,无人察觉
官方数据源直取星座连线数据用 d3-celestial 的 constellations.lines.json(Stellarium 官方图形,GeoJSON MultiLineString,ra 度/dec 度,Ser 分头尾两条需去重);行星贴图 Solar System Scope /textures/download/ 需带浏览器 UA;冥王星用 USGS 新视野号全球马赛克在 GitHub 教程仓库的镜像。生成 63 星座数据用 Python 离线转换去重,页面只内联结果 JSON
DOM 命中 vs 投影命中防重叠隐藏的标签不能按 DOM 判定;反之带文字的悬浮标签要按 DOM 矩形判定(需先在 updateLabels 每帧记录 o.sx/sy,隐藏时清空防残留)。星座连线无 DOM,走画布实时投影+容差拾取(矩阵需先 updateWorldMatrix(true,false) 防滞后一帧)
拖动+点选共存悬浮框 mousedown 时置 kbBoxSuppress 标志,全局 mouseup 的行星拾取先查该标志——否则拖动松手时 stale 的 lx/ly 会让 5px 判定误命中背景行星
比例尺(地图式)视距 d + FOV 求焦点平面 kpc/px,取 130px 对应长度再向下取 1/2/5×10ⁿ 整数刻度;单位随视距三档切换(kpc/光年/AU),AU 档按太阳系渲染比例 0.0024 单位=1AU 换算。10Hz 轮询与知识分档共用 setInterval,失败 try/catch 不影响主渲染
无头验证基线venv39 + headless Chrome + --use-angle=metal(macOS WebGL);等相机收敛(2.5s+)再断言;标签漂移类测试注意"drift"多是 pan 未收敛假象;截图后必须 ReadMediaFile 目检,断言数字对了不等于画面对

当天提交:a768cee(galaxy.html 492+/105-,mars.jpg 换 8K 真实照片,pluto.jpg 新视野号照片入库)。git 注意:.gitignore 忽略 *.jpg,新贴图须 git add -f。

2026-11-10 晚场 · 交互规则定型与知识库建设(自包含版,可复制)

适用范围:① three.js(WebGL)三维星图/场景中的「搜索对准 + 悬停资料卡 + 工具栏」交互;② Flask + SQLAlchemy 站点的「文档入库检索」管线。以下每条规则均按「问题现象 → 根因(为什么)→ 通用规则 → 可复制实现」四段式书写,代码可直接移植。验证方式:无头 Chrome + --use-angle=metal,相机收敛 2.5s+ 后再断言;88 星座全量搜索回归通过。

本文通用叫法本项目内变量/函数含义
跟随状态机aimCons / aimStar / aimSun / consSolo相机跟随星座质心 / 著名恒星 / 太阳 / 只显示目标星座的隔离标志
天球容器skyGroup / constSky挂了恒星与星座的 Group,自身带岁差四元数(EQ_QUAT)与公转补偿,持续旋转+平移
标签定位函数placeLabel把对象投影到屏幕并写 DOM:transform: translate(-50%, -130%)
拖动抑制标志kbBoxSuppress在可拖动悬浮框上按下鼠标时置位,防止松手误触发场景点选
子文档入库函数index_sub_article把一篇子文章(CKEditor HTML)切块写入检索索引

规则 1:跟随「正在运动的目标」时,每帧用世界坐标重写相机,禁止 lerp 缓动、禁止只算一次落点

问题现象:搜索"大熊座",相机先对准、几秒后目标自己漂出画面;点击太阳弹卡后太阳"跑走"。
根因:两个叠加错误。① 目标挂在持续旋转/平移的父组下(天球随公转补偿旋转,周期约 2.6 分钟),只算一次落点,父组一转就偏;② 相机用 lerp 逼近目标,存在稳态滞后 = 目标速度 / 缓动系数——目标速度 0.33 kpc/s 时,lerp 一帧的滞后就把月球甩出 1200px。
通用规则:跟随模式 = 渲染循环每帧执行;目标坐标一律取世界坐标(父组世界矩阵实时变换后的值);相机直接对齐(copy),不用 lerp;用户拖拽/点空白/Esc/新搜索任一操作即退出跟随。

// three.js 渲染循环内:
if (aimMode >= 0 && !userOrbiting) {
  // 关键①:局部坐标必须先乘父组世界矩阵,否则父组自带旋转会让方位角差 60°+
  _v.copy(localCentroid).applyMatrix4(skyGroup.matrixWorld);
  // 关键②:直接对齐,不用 lerp(lerp 稳态滞后 = 目标速度/k,运动目标永远差一截)
  const focus = focusOf(camDist);            // 相机注视点相对自身偏移
  view.tPan.copy(_v).sub(focus);             // 平移目标
  view.pan.copy(view.tPan);                  // 立即生效
  // 方位角也用世界方向反算,别拿未变换的原始 ra/dec 向量:
  _v.sub(sun.position);
  view.tYaw   = Math.atan2(_v.z, _v.x);
  view.tPitch = Math.asin(Math.max(-1, Math.min(1, _v.clone().normalize().y)));
}

规则 2:多个"自动对准"模式必须互斥,且所有退出路径集中清理

问题现象:搜完星座再点太阳,相机被两个目标来回拉扯。
根因:跟随标志散落在各功能里各自置位、各自清理,漏清就互相打架。
通用规则:全局只保留一组互斥的跟随状态;写一个 clearAim() 清空全部标志,每一个入口(用户拖拽、点空白、Esc、发起新搜索、选中其他天体)都调用它;各模式进入时也先调它再置自己的标志。

function clearAim(){ solo = -1; aimCons = -1; aimStar = null; aimSun = false; align = false; }
// 拖动开始 / 点空白 / Esc / 新搜索 / selectPlanet() 内:一律先 clearAim()

规则 3:LOD 淡出规则要给"被强制显示的目标"开豁免

问题现象:搜索高银纬星座(北斗等)后,星座球整个消失,只剩相机在转。
根因:距离驱动的淡出规则 fade=(投影半径-100)/90 在某些视角下算出 fade=0,把整个天球藏掉了——搜索态目标恰好最容易触发这种视角。
通用规则:任何"用户显式要求看到 X"的状态,都要绕过"距离/屏幕占比"类 LOD 隐藏,并给 X 的标签单独一条"强制可见"分支。

const eff = (solo >= 0 || aimingStar) ? 1 : lodFade;   // 搜索态豁免淡出
// 更新标签时:被引导的星座(i === solo)名字与逐星名不受 fade/距离窗口限制

规则 4:DOM 标签的悬停命中要按"文字真实渲染位置"的横带判定

问题现象:鼠标明明悬停在"金星"文字上,资料卡不弹。
根因:标签用 translate(-50%, -130%) 定位,文字实际渲染在锚点记录值 (sx, sy) 上方 5~27px 的横带内;若以锚点为中心做方形命中区,整条带都落在判定区之外。
通用规则:命中区 = 真实文字横带 × 半宽(按字数估算)。同一条带多个标签时取"鼠标离文字最近的"。

// 文字横带:锚点上方 5~27px(translate(-50%,-130%) 的视觉偏移量,按字号实测)
const inBand = (y > sy - 27) && (y < sy + 2);
const halfW  = text.length * 5.5 + 10;                 // 中文标签半宽估算
if (Math.abs(x - sx) < halfW && inBand) { /* 命中 */ }

规则 5:数组索引判空用 >= 0,禁用真值判断

问题现象:悬停比邻星(邻居数组第 0 号)永远不出卡。
根因:if(idx) 把合法的 0 当成假值跳过了。
通用规则:索引存在性判断一律 idx !== -1 或 idx >= 0。

if (idx >= 0) { showCard(neighbors[idx]); }   // 0 是合法索引!

规则 6:悬停链的自动关闭,只在"全 miss"时启动计时

问题现象(要避免的):自动关闭计时器在 hover 过程中误关卡片。
通用规则:每次 mousemove 先跑完整拾取链(行星标签→近邻星→深空天体→星座,分级取首个命中);任一命中则刷新卡片并重置计时器;全部 miss 才启动 15s 倒计时关闭。拖动悬浮框时置抑制标志,松手的那次 mouseup 不做场景点选(否则拖动结束的瞬时旧坐标会误命中背景行星)。

panel.addEventListener('mousedown', () => { suppress = true; });   // 拖动开始
window.addEventListener('mouseup', () => {
  if (suppress) { suppress = false; return; }                      // 松手不点选
  pickSceneAt(mouse.x, mouse.y);
});

规则 7:小图标状态渲染只保留一个函数;小字号禁配半透明/缩放

问题现象:工具栏部分开关图标状态不更新;图标发虚。
根因:同名的状态刷新函数被声明了两次,后加载的覆盖先加载的(JS 静默行为,不报错);12px 小字配 opacity<1 再经 transform 缩放会亚像素糊化。
通用规则:每个状态渲染函数全项目唯一(代码评审时 grep 函数名计数);小图标固定整数字号 + 不透明度 100%。

规则 8:Fullscreen API 必须是真实用户手势

问题现象:导览模式想自动全屏,JS 调 .click() 触发被拒。
根因:浏览器安全策略——requestFullscreen 只认真实用户手势事件(trusted event)。
通用规则:入口做成真按钮;自动化测试用 selenium ActionChains 物理点击而非 .click();监听 fullscreenchange 刷新图标状态。全屏与退出成对(导览开始全屏、结束退出)。

规则 9:文档入库检索——先删旧块再插入(幂等),HTML 用白名单清洗

问题现象:子文章在站内知识中心搜不到。
根因:入库管线只覆盖了主文章,子文章(CKEditor HTML)这类源从未纳入索引。
通用规则(Flask 通用,可移植):新增一类文档源时,① 切块函数复用现有"Markdown/HTML 白名单清洗";② 重编入索引采用先按 (source_type, source_id) 删旧块、再插新块,保证重复执行幂等;③ 路由层在创建/编辑保存时同步增量入库,别依赖手动重建;④ 检索默认源白名单显式列出新类型。

def index_document(doc_id, title, html, url, source_type):
    # 幂等:先清旧块
    Chunk.query.filter_by(source_type=source_type, source_path=str(doc_id)).delete()
    clean = strip_html_whitelist(html)      # 去标签脚本样式,留纯文本
    return add_chunks(source_type, str(doc_id), title, clean, url)

外部语料抓取陷阱:MediaWiki API 一次传多个 title 会命中 continue 分页,只返回第一页造成数据残缺——必须逐条请求,带 User-Agent,间隔 0.8–1.5s 限速;注意版权协议(维基为 CC BY-SA,须署名+回链原条目)。

回归基线与提交

88 星座全量搜索回归通过(搜索→点击→对准→稳定;唯一"失败"是批量测试相机转场 2.2s 不够,属测试节奏非功能问题)。天王星贴图换旅行者号真实照片(+2.2 倍对比度)。相关提交:63b15f5(已推 gitee master)。测试脚本当时存于本机 /tmp(临时目录,重启即失),复现请按本文代码段重写。

2026-11-11 · 资料卡「🔭 发现与传承」板块(四类数据源全覆盖)

需求:每张悬停/点击弹出的资料卡补充四项人文字段——发现时间、发现技术、主要神话、文章或诗词。

通用规则(可复制):卡片组件采用「可选数据块」模式——渲染函数只认 d.hist 键,数据层各来源自行组装;无值的行在渲染时过滤(h[1] && h[1] !== '—'),空数组不输出板块头,做到「无资料不显示、有资料必完整」。内容准确性策略:古典天体写"史前即知+最早文献出处",近代天体写"发现者+年份+仪器/方法",无神话/诗词的天体(球状星团、矮星系)宁可缺行也不编造。

数据源与填充方式:行星×9、太阳、月球——手写 4 行(史实均为主流天文史共识);近邻恒星×14——按标签名匹配,比邻星 1915 英尼斯照相底片、天狼 B 1862 克拉克折射镜等;球状星团×16、伴星系×3、电离氢区×4——与渲染数组按下标/名字一一对应;24 主星座——手写(托勒密 48 体系 + 希腊神话 + 中国最权威出典,如"七月流火""动如参与商");63 生成星座——从故事文本正则提取创立者与年代(拉卡伊/普朗修斯/赫维留),未命中再查托勒密 48 英文名白名单,兜底"近代星图创立"——新增星座零手工录入。

验证:node --check 语法过;selenium 11 项断言全绿(行星/日/月/近邻星/球状星团/伴星系/HII/主星座/生成星座各抽 1);最满卡片(地球,含 4 行)高 498px 不溢出;截图目检板块样式(紫色左边条与既有蓝/金块区分)。

2026-11-11 · 全 88 星座"不占位置"显示方案(索引面板 + 3D 弱显)

问题:88 个星座要全部可及,但 3D 天球塞 88 个名牌必然互相遮挡(24 主星座已是贪心防重叠上限)。
方案(核心规则可复制):名牌全量显示与访问入口必须解耦——天上"全都在"(弱显),名字"一个不多"(只在悬停/搜索时出现),浏览入口收进折叠 UI 面板。三件事:

组件实现要点
♈ 折叠索引面板底框加一个图标(收起时唯一成本);点击展开 356px 宽分组面板(北天拱极/春夏秋冬/南天兜底),每项=中文名+IAU 三字母代码;面板项数据全部从 CONSTELLATIONS 运行时生成——88 个英文名→代码映射表 + 分组 token 表,"南天与其他"组=未被分组引用的余项自动归入,新增星座零维护不会漏
英文名匹配的坑includes('Leo') 会误吞 Leo Minor、includes('Triangulum') 误吞 Triangulum Australe——匹配键按长度降序取最长命中(consTokenOf)
3D 弱显63 个未显现星座:星点 sprite opacity=userData.op×0.16、连线 0.10、visible=true;名牌逻辑不动(hidden 仍不显示名字)——"不占位置"靠名牌不上天实现,不是靠不渲染
悬停放行pickConstellation 去掉 hidden 过滤(保留搜索隔离过滤)——弱显星座碰到即出资料卡
状态恢复搜索隔离(applyConsSolo)会把弱显星座一并藏起来,restoreConsSolo 末尾必须重放 applyConsWeak();搜索显现(revealConstellation)的 sprite 进入 constSprites 淡出管理后自动恢复全亮(userData.op 未被弱显改动)
面板点击防误选面板项 onmousedown 置 kbBoxSuppress=true——消费掉全局 mouseup 的行星拾取(同底框拖动抑制机制)

验证:selenium 断言全绿——面板 88/88 项、弱显 54/54 可见且连线透明度 0.1、弱显名牌隐藏、真点击 ♈ 展开/点项飞往(goConstellation 路径 soloOn=true)面板自动关、Esc 后弱显 53/53 重放;截图目检分组面板与飞行画面。FPS 25(headless 基线正常范围)。

2026-11-11 · 人类探索进程演示:足迹图层 + 「探索之旅」导览(范围球膨胀隐喻)

需求:演示人类探索宇宙的进程与活动范围扩张。核心视觉隐喻——以太阳为中心不断膨胀的"人类活动范围球"。

通用规则(可复制):①图层组必须挂 scene 并每帧拷贝太阳位置——不能挂太阳 sprite 节点下,太阳 farDim 缩放剧烈(0.00005–0.06),子节点会跟着变形;②"活动范围随时间膨胀"用目标半径+每帧 6% 平滑逼近实现,导览只需改目标值,任何引擎可移植;③跨 10⁸ 量级(400 km→162 AU)的演示不要追真实比例——用分级圈表达,每圈独立标注真实数值;④图层标签只在相关视距窗口显示(dSun<1.2),银河视角自动隐藏防糊屏。

组件实现
足迹图层(🛰 开关)火星范围圈 1.66 AU(橙)+ 日球层顶圈 120 AU(紫)+ 旅行者 1 号标记(真实方向 RA≈17.3h dec≈+12°,162 AU≈0.39 场景单位——太阳系压缩比 0.0024/AU 下真放得进去)+ 范围球(青色线框)
探索之旅(👣 导览)导览引擎泛化为双模式(tourMode: mw/human,站点表=JSON 配置共用补间/全屏/结束逻辑);HUMAN_RACE 14 站时间轴(1957 斯普尼克→2026 此刻),每站 run() 设相机机位+humanRangeTarget,范围球随之膨胀
数据准确性旅行者 1 号 162 AU 为 2026-11 约值(年增 ~3.6 AU),标签注明"信号光速回传需 22.5 小时";事件按资料卡规范:年份+技术+意义
验证selenium 全绿:图层开关/图标态、4 标签太阳系视角全显、导览双模式标题正确(1957 斯普尼克→2026 此刻)、末站 rangeTarget=0.389、结束图层保留;截图目检终章"范围球套太阳"画面。全程 14 站×8s≈2 分钟,可直接录屏

2026-11-11 · 探索之旅增强:历史照片卡 + 巡视器/着陆器标记

需求:①导览站点配真实历史照片;②月球/火星上的巡视器此前不存在于场景,需新增标记。

规则实施方法(可复制)
历史照片版权策略NASA 静态图基本无版权(仅需署名),ESA/部分 JPL 艺术图有限制——统一站内自存(/static/img/history/,不热链),每张带 credit 行,艺术想象图明确标注。下载管线:images-api.nasa.gov 搜索→collection.json 取 ~medium.jpg,12 张全部成功(斯普尼克 NASA 9248168、足迹 AS11-40-5878、冥王星 NASA/JHUAPL/SwRI、暗淡蓝点等)
照片卡 UI固定 216px 卡贴在导览条上方(定位复用搜索弹层模式:底框上方贴边,不足则放下方);仅探索之旅模式且有 photo 字段的站点显示,stopTour 必隐藏;与站点同生命周期不打断飞行
小天体上的标记定位经纬度→球面方向(phi/theta 公式)×半径×1.15;标记尺寸按相机距离反比缩放 clamp 到 [r×0.8, 0.002]——任何缩放下约 10px 可见(复用 starMark 思路);标签窗口按天体分档(月球 dCam<0.01,火星 <0.03)
精度诚实原则月火贴图未做 UV 定向校准,着陆点卡片注明"方位示意",不做假精确
悬停优先级巡视器拾取必须排在行星标签横带判定之前——横带判定会盖住旁边的小旗标(pickRover 用 16px 圆形容差而非文字横带)
数据规范6 个着陆点(阿波罗 11、玉兔二号、好奇号、祝融号、机遇号、毅力号+机智号)资料卡严格按《资料卡生成规范》四字段

验证:selenium 全绿——月球 2/2 标记+标签、火星 4/4、阿波罗卡含 hist、探索之旅 1969 站照片卡显示且 credit=NASA;截图目检月球双旗标与导览照片卡同屏。工作模式补充:NASA 图片 API 逐条请求有效,搜索词给 NASA ID(如 as11-40-5878)命中率最高。

2026-11-11 · 三项修复与收尾:抖动、遮挡、照片补全

同日对探索之旅功能的三个打磨,均为高频交互病,规则可复制。

问题现象根因(为什么)通用规则 → 可复制实现
人类足迹层抖动图层同步(跟随太阳 + 标签投影)挂在 10Hz 轮询,而相机/天体是每帧运动——更新频率不一致,元素以 120ms 步率追赶画面就是跳帧抖动(与早前"星座文字抖动"同类病)任何跟随运动目标的视觉元素必须与渲染同频:移入 requestAnimationFrame 渲染链(本页即 updateNeighbors() 之后、try/catch 保护块内),从 setInterval 移除。诊断技巧:采样元素 transform 变化次数/采样间隔,修复前应约 1/6 有变化,修复后 21/21 全变化
导览照片卡遮挡导览条定位用的是图片加载完成前的瞬时高度(仅标题文字几十 px),算出"贴条上方 8px";图片加载完卡片向下长到 190px,底部压住站名文字含图片的悬浮卡必须等图加载后再定位:img.complete 为 true 直接量(已缓存),否则挂 img.onload 重定位一次。验证:强制渲染多站点量卡片与导览条间隙,恒为 8px 零重叠
三站缺历史照片NASA 官方档案对苏联航天时代几乎无覆盖(搜 voskhod/salyut 无结果)——政治年代造成的数据源偏斜数据源兜底策略:NASA images-api 为主(搜索词用 NASA ID 命中率最高)→ 空缺走 Wikimedia Commons API(srnamespace=6 搜文件)→ 仍无则用同时代纪念物(邮票/官方艺术图)并在说明中如实标注性质。工程坑:本机 curl 对 upload.wikimedia.org DNS 间歇故障,--resolve 指定 IP 可绕过

当日全量:资料卡「发现与传承」四字段(8 类数据源)、工作文档写作规范(docs/ 三文档 + AGENTS.md)、88 星座索引面板 + 3D 弱显、人类足迹图层 + 探索之旅 14 站导览、12→15 张历史照片、6 个巡视器/着陆器标记、抖动/遮挡两项修复。探索之旅现全长 14 站 × 8 秒 ≈ 2 分钟,站站有真实照片,月球/火星站有着陆点旗标,可直接录屏。

2026-11-11 下午场 · 文章页排版型故障修复(500 / 非法栅格类 / 模板分支写反)

问题现象:/sub_article/64 打开即 AttributeError 整页报错(未登录访问 500);登录后正文栏只剩约 340px 宽,表格一列一个字竖排,右侧大片空白。curl 验证:匿名 200 但 body 是调试错误页;登录态 DOM 量得 .mailbox-right 仅 465px(设计应为容器 83%)。

根因(5 个,链式暴露):① views.py:252 对未登录用户直接取 current_user.id——AnonymousUser 无 id 属性必崩,是用户看到的"排版不正常"真身;② 修复①后立刻 NameError: markdown——生成 content_html 的分支从未被执行过(存量数据 content_html 早已存在),import 缺失潜伏至今;③ bleach 白名单缺 blockquote/img,引用块与配图会被过滤;④ 模板两个渲染分支写反(有 content_html 时渲染原始 content、没有时渲染 content_html);⑤ 左侧栏用非法 Bootstrap 栅格类 col-md-1.3(主文章页是 col-md-1.2)——Bootstrap 无此类,左栏掉出栅格流,右栏 col-md-10 被挤压。主文章页 /main_article/23 与用户截图确认同病。

通用规则:1)任何取 current_user.id 的视图必须先过 is_authenticated(本站 7 处旧代码均为此写法,新增代码必须照抄);2)"从未执行过的分支"等于没有代码——改完触发路径必须实测一次;3)HTML 白名单要跟着内容资产走(引用块/配图是文章系统一等公民);4)Bootstrap 栅格类只有整数 1–12,"col-md-1.3" 不是"1.3 列"而是"无此类";5)调试渲染类页面(debug toolbar)会被用户误认为"排版坏了"——先 curl 看 HTTP 状态再谈 CSS。

可复制实现:

is_author = current_user.is_authenticated and current_user.id == current_sub.author_id
from markdown import markdown   # 潜伏 NameError 的补法:分支级 import 与视图同文件头
allowed_tags += ['blockquote', 'img']; allowed_attrs['img'] = ['src','alt','style','width','height']
# 模板:{% if content_html %}{{ content_html|safe }}{% else %}{{ content|markdown|safe }}{% endif %}
# 栅格:<div class="col-md-2 mailbox-left">(左栏)+ col-md-10(右栏),左栏 max-width:180px 由行内样式约束

验证:匿名 curl 200、blockqu­ote/img 保留(重建 content_html)、selenium 登录态 1843px 窗口两页正文栏 1215px、表格全宽。提交 d28a9bd。

2026-11-11 下午场 · 导览相机准确性核验(24 站全量实拍 + 4 处修复)

方法:无头 Chrome 把十站银河(10 站)与探索之旅(14 站)完整跑一遍,每站等相机补间收敛 3.5s 后截图逐站目检——共 24 张。导览引擎每站 8s,相机目标在站点表 run() 里改 view.tDist/tPan/tPitch。

问题现象 → 根因 → 修复:

站现象根因修复
mw3 比邻星画面全是银河盘星点墙,比邻星不在画面tPan 误用天球组局部坐标 o.rel 当世界坐标(其他处都用 o.v)——近域恒星数据挂在 skyGroup 局部系,focus 落到银心附近,相机距太阳 8.3 kpco.rel→o.v;starLabelsOn 强制开;starMark 打★标记并缩到 0.0006(默认 0.004 贴脸糊满屏)
mw4 太阳系俯瞰2 万颗 Gaia 星点糊满屏,太阳成白团,内圈轨道全被盖① 相机 0.15 恰在近域恒星层表面(500ly=0.153)看穿最密处;② 太阳光晕 sprite 角直径恒定 ~7°,地球轨道(1.6°)深埋光晕内;③ 小行星带 5 万颗 10px 点加色叠加成"金色甜甜圈"tDist 0.15→0.088;新增压光体系 sunGlareK 0.3 / localGainK 0.12 / beltGainK 0.15(带层拆独立 uniform)/ orbitBoostK 3
mw2 猎户支臂太阳在画面底边外tPan 偏移 (0.4,0,0.5) 过大减半为 (0.2,0,0.25)
hm1 斯普尼克历史照片卡消失(2–14 站正常)requestFullscreen 异步改窗口尺寸,第 1 站 placeHp 用旧窗口坐标定位后窗缩,卡跑屏外——与"img.onload 重定位"同源但那次只修了图片加载resize + fullscreenchange 监听,显示期间持有 hpPlacer 重放

通用规则:1)双坐标系项目里,跨层传坐标必须写明"局部 or 世界"——本模块 skyGroup/galaxy/humanGroup 三个组各有局部系,世界坐标一律用 o.v(每帧 applyMatrix4 刷新)而不是 o.rel;2)跟随运动目标的视觉元素必须与渲染链同频(每帧),异步事件(全屏切换/图片加载)只做"重定位触发器",单次定位必竞态;3)教学类视角要为主角清场:多层叠加场景(星场/尘埃/光晕/碎片带)做"特写站"时,用系数压光 + 中央复位比逐站写还原代码可靠——系数集中在 step() 里先复位默认值、run() 自行覆盖;4)判定相机准确与否的金标准:截图目检画面内容与站点文案是否相符,坐标数字对了不等于画面对。

可复制实现:

// 压光四系数(导览态)
let sunGlareK = 1, localGainK = null, beltGainK = null, orbitBoostK = 1;
// step() 开头:sunGlareK = 1; localGainK = null; beltGainK = null; orbitBoostK = 1;   // 中央复位
// stopTour() 同样复位,防残留到手动浏览
// animate 内:localGainUniform.value = localGainK != null ? localGainK : (1.0 - 0.95*t);
//   beltGainUniform.value = beltGainK != null ? beltGainK : 1;
//   for (const p of planetObjs) if (p.orbitRing) p.orbitRing.material.opacity = 0.3 * orbitBoostK;
// 需要压光的站点 run():sunGlareK = 0.3; localGainK = 0.12; beltGainK = 0.15; orbitBoostK = 3;

验证:修复后 4 站重拍截图确认(比邻星:太阳+行星+★标记同框;俯瞰:同心轨道圈清晰;hm1:照片卡稳定显示)。提交 8e08ce1。

2026-11-11 下午场 · 知识入库管道落地:P0 三项 + P1 三项(方案见 /artcles/256)

架构:文章 = 知识本体,模块 = 交互界面,中间加一层结构化 JSON——app/galaxy/data/kb/<topic>.json,Flask 通用端点 /galaxy/api/kb/<name>(正则 [a-z0-9_]+ 白名单防路径穿越),前端按需 fetch。galaxy.html 已 3900+ 行,知识内容绝不硬编码进页面;文章修订后重生成 JSON 即同步。

批次功能数据源模块入口
P0-1宇宙距离阶梯层:视差 3.07 kpc + 造父 49.1 kpc 真实尺度球壳(Ia/红移超出 75 kpc 模块尺度不画示意图,只进卡)sub72 测距方法全谱底框 📐
P0-2旋臂标注点击卡:5 条臂 + 银心/中心棒/隐匿带/暗物质晕悬停卡(密度波/示踪方法/未解之争)sub77 银河系结构银河视角悬停臂名标签
P0-3多波段切换:可见光/红外/射电三态——尘埃隐去(隐匿带消失)、银心×1.6、星云×1.7、全屏着色蒙版sub81 + 主文章第九节 NASA 图底框 🌈
P1-1恒星演化卡:7 光谱型档案 + 5 阶段时间轴;近邻星悬停卡自动加"它的一生"行sub76 恒星的一生近邻星卡「✨ 它的一生」
P1-2足迹层对齐:导览 14 站「📖 阅读原文」回链(sub70/74/75/79 按主题映射);巡视器卡改链子文章sub70/74/75/79探索之旅导览条
P1-3今晚观星:12 个月×3 座当季星座推荐,一键飞 3D 星座视角 + 观星要点sub85 观星入门底框 🔭

通用规则:1)JSON 里禁止裸 ASCII 双引号内嵌——中文内容内嵌引号一律用『』/“”,否则 json.load 直接炸(本次 spiralarms.json 踩过:字符串里 "恒星产房" 未成对转义);2)面板/卡片 z-index 生态:infoCard z1300 > 浮层 z1210 > 底框 z1200——开浮层必须先 hideInfoCard(),fixed 定位面板必须显式给 inset(缺 top/right 会落到静态定位处,视口外不可见);3)双向闭环验收:模块每个入口回链 /sub_article/xx,文章开头插「配套交互」blockquote(入库前清 content_html 强制重生成,过 bleach 新白名单);4)数据分级原则:真实比例模块不画压缩示意圈——超出尺度的量(3000 万光年/红移)进知识卡文字,不进 3D。

可复制实现(Flask 通用管道端点):

@galaxy.route('/api/kb/<name>')
def api_kb(name):
    if not re.fullmatch(r'[a-z0-9_]+', name or ''): abort(404)
    data = _load_json(os.path.join('kb', name + '.json'))
    if data is None: return jsonify({'error': 'kb topic not found'}), 404
    resp = jsonify(data); resp.cache_control.max_age = 3600
    return resp

验证:7 份 kb JSON 全部合法、端点 200/404 正确、浏览器逐项实测(球壳/标签/卡片/三态切换/演化时间轴/月面板/导览条回链);sub72/76/77/81/85 五篇配套交互指引已入库索引。底框图标现 14 个:🔍♈👣🛰📐🌈🔭⤳⊙🚀🪐🏷🎵⛶。build v20261111-04。

2026-11-12 · 知识文库扩建:2 篇新增 + 2 篇扩写 + 全系列重排(27 篇定版)

背景:六线盘点发现三个缺口——宇宙学本体无专篇、多信使只有一句、"做中学"任务层缺失。当日全部补齐。

动作对象内容
新增sub89《宇宙的过去与未来》→ 第六篇138 亿年时间线表、暗物质/暗能量现状、四种宇宙结局剧本、确定与未解清单、原理速览 5 条
新增sub90《做中学:12 个可以交作业的研究任务》→ 第二十五篇A–E 五组任务卡(核心问题/材料/步骤/产出/升级挑战)、好项目阶梯评价表、可复印记录表模板;安全红线(望远镜禁对太阳)
扩写sub73 第二十三篇新增第八维度"反哺物理与数学":极限实验室/数学出题史表/先算出来再找出来的闭环
扩写sub81 第十二篇新增末节"多信使天文学":引力波/中微子/宇宙线/GW170817 接力案例
重排主文章 23 全部子文章(26→27 篇)按"定位→天体档案→工具方法→文明叙事→意义实践→做中学→工程附录"认知动线重排 order_index;系列第 X 篇编号正则全量重编,与显示次序一致;KB index_sub_article 逐篇重索引

方法:DB 直写脚本(先断言幂等再写入),页面 curl grep 关键词核验渲染;主文章 23 同步补"11-10~11-12 迭代补记"并更新星座节表述(八大→全天 88)。本次为数据层变更,未涉及模块代码。

© 2025 铠牛 - 项目印记 | 预测未来的最好办法,就是把它创造出来