切换外观
驻龄 · 动起来之前:参考、数据、故事
状态:已实现,待用户审阅。2026-10-28 第四轮候选 03 驻龄按第三轮的六步流程(reference → data → story → motion → direct → ship)做成完整形式 sketches/age.html;第 1–4 节在动之前写定,第 5–6 节是实现与验证。它是复盘工具,不承载作答。两层法则与交互标准见 CONCEPTS.md,同类页面的完整范例是让位图与两周小倍数,交互取共用模块 motion/ix.mjs;候选的来历见 INSPIRATION.md。
1 参考:草图现在怎么讲驻龄
草图的讲法。 探索代理的静态草图只画 composite 的 f26(8/16 00:00):一张 8×8 的方格,一格一份,按名次从左上填起;格里同心方框的圈数是这一份在这件事上连续待了几轮(上限 12 圈),从第一轮起一直在的涂实;左栏大字 19/63,下面写守恒“让出 8 + 留白减 11 = 新给 19”;右栏按名次列每件事的格子年龄(一排小方块加一句话)。只用墨黑一种颜色,圈数是唯一的变量。用户看过说可以往下做。
它讲对了的。 一眼看得出“沉”与“新”:圈多的格发黑,只剩一道外框的发白;名次表把黑白拆回每件事。这是别的形式都没有的关系:让位图只画这一轮谁涨谁缩,小倍数要逐帧翻,都看不出某一份已经十轮没动。
动起来以后讲不动的。
- 按名次填,格子会自己挪。 名次一变,下面所有块都要移位,一格里的圈数跟着换成别的份额的圈数。小倍数量过同一种填法:28 轮里位置换了主人 941 次,份额真正易手只有 357 次(multiples-motion.md)。驻龄说的就是“这一份没挪过”,画面里的格子要是会挪,就和自己的主张打架。
- 身份靠单双底色。 草图给奇数名次的事垫浅底;格子一旦不按名次连成一串,相邻的两件可能同色,分不清谁是谁。
- “实心 = 从第一轮起”只在 f26 成立。 草图把
age ≥ 26涂实。逐帧走时,f02 的每一份都是“从第一轮起”,按这个规则 f02 会几乎全黑,暗示的恰好相反。 - 大数字只有一帧。
19/63是 f26 的;每一轮都该有自己的“新给 / 在用”,并且和格子里只剩一道框的格数对得上。
从参考搬什么,不搬什么。
| 要素 | 出处 | 搬过来 | 不搬 |
|---|---|---|---|
| 网格从不散 | #90 Molnár《(Des)Ordres》:17×17 格子位置完全规整,灰度全靠每格画了几圈 | 64 格的位置两周不变;换轮时格子不动,只有圈数变 | 有的方框略微歪斜:在数据图里歪斜会被读成数据 |
| 圈由外向内 | #90 的同心方框 | 新给的一份只有最外一道框;留下来的每轮在最里面多画一圈,像沉积,圈越多越黑 | 彩色版的个别红圈:颜色只给一种含义,这里不用 |
| 空缺 | #90 有的格子整个空着 | 留白就是空格(虚线),不画圈 | — |
| 只靠密度、不贴标签 | #91 Molnár《Interruptions à recouvrements》:叠印处加深,不画框、不贴“新”字 | 新与旧只靠圈数分;清出的格只留一道灰框,0.5 秒散去 | 叠印本身:两层叠在一起会让人数不清圈 |
| 街区与先后 | #93 Tarbell《Substrate》:细线把平面切成街区,后来的从先来的地盘里切出来 | 同一件事的格子垫一块浅底,格缝里连成一片,不同的事之间留一道纸色缝,像街区;后来的事落在别人清出、或一直空着的格里 | 沙色渐变与斜向裂纹(装饰),以及“谁从谁那里切出来”:回执不记配对,见第 2 节 |
| 节拍 | 让位图:切点硬切、0.4 秒第一个动作、全速 3.2 秒 / 半速 1.6 秒 / 逐帧 0.9 秒、讲一遍就停 | 同一张时间表,三个动作换成清出、加圈、新框 | 灰格沿行飞进中线的池:这里格子不动,没有池 |
| 灰影 | 小倍数:走掉的格在原位留灰影再散 | 清出的格在原位留一道灰框 | 播放框拖尾(这里只有一张方阵) |
| 不画配对 | 让位图、拨盘 | 卡与侧栏一律写“同一轮清出 / 新给”,不写“给了谁”“来自谁” | — |
概念层的占比:一成以内,静止时为零。 它是复盘工具,会反复打开、逐格比对;量就是 64 格本身,不需要另外的质感。概念层只有一样:被清出的格在原位留下的一道灰框(16% 灰、1 px,0.5 秒散去;半速 0.25 秒,快放 0.12 秒),个数等于这一轮清出的份数;它只是线,任何时刻都不到画面的 1%。不飞点,不留余辉,静止时整张图没有任何东西在动。
2 数据:两周里每一份待了多久
口径。 和页面同一套推导:用 node 直接导入 variants/shared.mjs(replayScenario 的 records 与 currentAsk)现算,问题的开闭用 stage.mjs 的 askLife 同一算法。脚本只在本机,不提交。帧号 fNN 就是 composite 的序号 NN;story 的第 1 轮是 cold-01(序号 1)。
“连续待了几轮”的精确规则。 只看根层(子层另有自己的 64 份,这张图不画)。一件事在第 i 帧的份额是 s_i;从这一帧往回取层:m₀ = s_i,m_k = min(m_{k−1}, s_{i−k}),遇到 0 就停。它的第 u 份(u 从 0 起,最老的在前)驻龄 = 满足 m_k > u 的层数。等价的说法是一叠:一件事变小时先让出最年轻的份,变大时新给的份从 1 轮起数。所以:
- 新给的份驻龄是 1;没变的份每轮加一;
- 一件事份额连续 k 轮都不低于 n,它就有 n 份驻龄至少是 k;
- 每件事的格子按驻龄从老到新排,名次表里的小方块就按这个顺序;
- 换 id 不算同一件。 身份就是条目 id:并入产生的新条目,份额从 1 轮起数(composite f22:复用绕过 6 份与旧的待审 PR 1 份并成新的待审 PR
pr-reviews-08146 份,6 份都是 1 轮;头虱 1 份已待 13 轮,与旧的开学文件并成signe-school-start,也从 1 轮起)。理由:并入不守恒(f22 两件 7 份并成 6 份),说不出哪一份是带过去的;而“沉淀”要说的是同一件事一直占着。并入的去向回放里有(snapshot.items[id].merged_into),所以新条目落在被并的那几格上,位置接得上,驻龄不接; - 撤下后以同一 id 恢复,中间有一轮是 0,驻龄从 1 重新数;只改名或改摘要(同 id)照常累计;
- 留白不计驻龄;
- 实心:从第一轮起一直在(驻龄 = i),并且已经超过 12 圈。圈数画到 12 为止,12 以上的格看起来都满了,实心把其中“从第一轮起”的那几份单独标出来。composite 在 f13 第一次出现实心,story 在序号 13(f11)。
格子不动:格位怎样分。 驻龄是数据,格在哪里是画法。64 格是固定的位置,一格一份:
- 第一轮从空开始,按名次在阅读顺序上贴着长(一件事长成一块);
- 之后每一轮先清出:变小的事交回它最年轻的几格(同龄时先交回贴着自己的格最少的那格,再交回离它最老那几格最远的),撤下与并入的整块交回;
- 留下的格驻龄加一;
- 再按这一轮的名次,变大的事一格格认领空格:先认贴着自己地盘的,再认离自己最近的(曼哈顿距离),同等时选四周空格多的;新出现的事若有被并入的来源,先落在来源刚交回的格上,否则落在四周空格最多的那格。
这样一格只在份额真的易手时才换主人,逐格比对时格里的驻龄与上面的规则 28 轮全部一致(composite、story 各核对一遍,0 处不符)。新主人落在哪一格只是画法的选择:哪里空着、离自己的地盘近;回执只记每件事增减多少,不记谁让给谁,所以“主水管占了猫看病清出的格”不是事实,卡与侧栏一律写“同一轮清出 / 新给”。
两周的总账(composite)。 28 轮、在用的份合计 1689 份·轮,平均驻龄 6.45 轮。新给的份合计 306,其中冷启动 f01 的 57;冷启动之后每轮新给中位 6、最多 26(f18),只有 f27 一份没新给。
| 量 | 值 |
|---|---|
| 从第一轮起没挪过的份(f01 起逐轮) | 57 46 39 36 27 25 23 23 23 23 23 23 23 23 23 23 23 22 20 19 18 15 9 6 6 6 6 6 |
| 其中一份没丢的一段 | f07–f17,连续 11 帧停在 23 |
| 变实心的一帧 | f13:这 23 份驻龄 13,第一次超过 12 圈 |
| 两周里被清出的最老一份 | 母亲换水管,f24 清出的 3 份,待了 23 轮 |
| 平均驻龄最高 | f21,9.3 轮;之后 f23 起急降,f26 是 5.9 |
故事要用的几帧(composite)。 每行:新给 / 在用、从第一轮起、清出(其中最老)、问题。
| 帧 | 时刻与旁白 | 新给 / 在用 | 从第一轮起 | 清出 | 这一轮的问题 |
|---|---|---|---|---|---|
| f01 | 8/3 12:00「母亲来电、猫打喷嚏,#13350 等复审」 | 57 / 57 | 57 | 0 | 新登记 5 个 |
| f13 | 8/9 12:00「回城日:rc1 的接缝」 | 4 / 64 | 23,变实心 | 3(rc1,1 轮) | 开着 7 个 |
| f21 | 8/13 12:00「#13567:复用防护可被绕过」 | 7 / 62 | 18 | 9(最老 20 轮:小龙虾派对、问答回复) | 失效「后刹排气定哪天?」,新登记「VAB 报了吗?」 |
| f22 | 8/14 00:00「热浪第一天:药量超过失明线」 | 19 / 64 | 15 | 17(最老 21 轮:小龙虾派对 3 份底层) | 失效 3 个,新登记 2 个 |
| f23 | 8/14 12:00「打了诊所;母亲自己定了」 | 5 / 39 | 9 | 30:母亲换水管 24 → 6 清出 18(12 份待了 5 轮、6 份从第一轮起待了 22 轮),猫看病 24 → 12 清出 12(全是 1 轮的) | 答掉「Smilla 多吃了药:打诊所?」「#13572 堵复用绕过:批准?」,失效「母亲九月住哪?」,新登记「三个旧 PR 推到哪天?」 |
| f24 | 8/15 00:00「车道边湿了一整周」 | 15 / 51 | 6 | 3:母亲换水管 6 → 3,3 份从第一轮起待了 23 轮 | 开着 3 个 |
| f25 | 8/15 12:00「周六早上:水表在转」 | 6 / 52 | 6 | 5(骑车 3、FTPS 计时 2,都是 1 轮) | 开着 3 个 |
| f26 | 8/16 00:00「车道下的铜管裂了」 | 19 / 63 | 6 | 8:猫看病 12 → 6 清出 6(最老 8 轮),Ture 发烧 3 → 1 清出 2 | 新登记「主水管:挖开换还是免挖?」(8/17 10:00 前)与「#13596 计时修复:批准?」 |
f26 的守恒:让出 8(猫看病 6 + Ture 发烧 2)+ 留白减 11(12 → 1)= 新给 19(主水管 +12、FTPS 计时 +5、后刹 +2)。一般地,新给 = 让出 + 动用的留白;留白变多时写成“让出 = 新给 + 退回留白”(f23:让出 30 = 新给 5 + 退回留白 25)。新给的份都是只有一道框的格,所以“新给 / 在用”的分子永远等于方阵里只剩一道框的格数。
f26 每件事的格子年龄(按名次,老 → 新):主水管 24 份 = 11 · 3×5 · 2×6 · 1×12;FTPS 计时 6 = 3 · 1×5;待审 PR 6 = 5×6;master CI 6 = 26 · 4×5;猫看病 6 = 26 · 12×2 · 9×3;后刹 3 = 7 · 1×2;母亲换水管 3 = 26×3;65KB 上传 3 = 3×3;其余 6 件各 1 份:Ture 发烧 8、开学文件 5、漫画 9、风扇 3、Kerberos 26、rc1 15。从第一轮起的 6 份:master CI 1、猫看病 1、母亲换水管 3、Kerberos 1。
手写情境(story)对照。 37 帧,36 轮;在用合计 1910 份·轮,平均驻龄 9.50 轮,比 composite 沉得多:件少块大,f06、f10、f13、f14、f17、f18、f21、f24、f27、f31、f32、f34 这 12 轮一份没新给。从第一轮(cold-01,发布 6 份)起:cold-01–f06 是 6 份,f07 起是 3 份,一直到最后:发布底下那 3 份两周没挪过。驻龄超过 12 却不是第一轮的份很多(f16–f18 各 28 份),所以 12 圈以上的格大片发满,这是这份数据本来的样子。
| 帧 | 时刻与旁白 | 新给 / 在用 | 从第一轮起 | 清出 | 这一轮的问题 |
|---|---|---|---|---|---|
| f11 | 10/15 21:00 | 6 / 61 | 3,变实心 | 0 | — |
| f15 | 10/17 10:00「陪妈妈改约周日11点,双方已确认」 | 12 / 46 | 3 | 27:试用 24 → 6 清出 18(12 份待了 14 轮),发布 12 → 3 清出 9 | 答掉「周六 11 点见妈妈,可以吗?」,新登记「周日 11 点见妈妈」 |
| f18 | 10/18 17:20「陪妈妈参观东馆已完成,厨房返修后待观察」 | 0 / 52 | 3 | 0 | 答掉「周日 11 点见妈妈」 |
| f19 | 10/19 09:10「暂存口径再次延期,扩大演示范围待决定」 | 21 / 55 | 3 | 18:妈妈 24 → 6(12 份待了 4 轮、6 份待了 15 轮) | 新登记「周三演示前,开放范围怎么定?」「苏禾改约周四 19:30」 |
f19 的守恒:让出 18 + 留白减 3(12 → 9)= 新给 21(发布 +9、试用 +6、苏禾 +3、链路 +3)。f15 是:让出 27 = 新给 12 + 退回留白 15。
格位的碎。 按上面的分法,一件事的格大多连成一块;份额紧、空格分散的轮次会碎:composite 每帧比件数多出的碎块合计 161 块、最多一帧 10 块(f07–f09 一带,16–18 件挤满 64 格),f26 主水管 24 格是一整块;story 合计 56 块、最多 5 块。碎是这种画法的诚实代价:格子不挪,新来的只能落在空着的地方。
3 故事
一个点。 预算是一层层沉下去的:每轮换手的只是薄薄一层,留下的每轮多一圈;一件事了结,底下沉着的才腾出来,腾出来的位置归下一件急事,从一圈重新数起。(composite:第一轮给出的 57 份里,有 23 份从 8/6 到 8/11 一份没丢;8/14 早上你给诊所打了电话、母亲自己定了,12:00 那一轮清出 30 格,其中 6 格沉了 22 轮;一天半后主水管长到 24 份,此刻 63 份里 19 份是这一轮才给的。)
英雄数字:19 / 63。 这一轮新给的份数 / 此刻在用的份数;分子等于方阵里只剩一道框的格数。每一轮都有一个(composite 冷启动之后中位 6 / 64);f26 的 19 是第二周里最大的一次之一,故事停在这里。旁边写守恒“让出 8 + 留白减 11 = 新给 19”,下面一行“从第一轮起没挪过 6 份”。数字放在侧栏“这一轮”的顶上(字高 56 px,与让位图同一处),分母灰色、平账时转墨;讲述与连播里分子随新框一格格数上去,逐帧在平账时硬切。
三拍(composite 共约 23 秒,讲一遍就停在 f26;帧号是页面常量,不写进数据)
| 拍 | 时长与帧 | 发生什么 | 观众此刻 | 在动 / 只在节拍上落定 |
|---|---|---|---|---|
| 0 | 0–0.4 秒,f00 | 64 格全是虚线留白 | 一张空的 8×8 | 不动 |
| 1 一圈圈沉下去 | 0.4–9.5 秒:f01 半速 1.6 秒;f02–f21 每轮 0.32 秒,f13 多停 0.5 秒,f21 多停 0.6 秒 | f01 按名次画出 57 道外框,英雄数字数到 57 / 57;之后每轮留下的格在最里面多一圈,新给的从一道框起;f13 从第一轮起没挪过的 23 份圈满 12 圈、涂实,旁注写出来;它们到 f17 一份没丢,f18 起被一份份挖走,f21 剩 18 | 预算在沉:每轮换手的只是薄薄一层,方阵一轮比一轮黑 | 动:每轮的清出、加圈、新框(快放里各约 0.1 秒)。落定:每轮的英雄数字、名次表、实心 |
| 2 挖开 | 9.5–15.1 秒:f22 半速 1.6 秒,f23 全速 3.2 秒再停 0.8 秒 | f22 猫看病 12 → 24、十件小事退场,底层 18 → 15;f23 第一个动作是清出:母亲换水管与猫看病的 30 格一格格清空,其中 6 格是实心的;留白 0 → 25;英雄数字 5 / 39,“让出 30 = 新给 5 + 退回留白 25”;问题列表一帧切换:划去「Smilla 多吃了药:打诊所?」与「#13572 堵复用绕过:批准?」,「母亲九月住哪?」转灰注“失效” | 事情了结,沉了 22 轮的格子才腾出来 | 动:30 格清空(每格 0.24 秒,相邻错开不超过 20 ms)、灰框散去、“让出”逐格计数。落定:平账时的分母、守恒等号、底层 9、问题列表 |
| 3 从一圈重新数起 | 15.1–23.0 秒:f24、f25 半速各 1.6 秒,f26 全速 3.2 秒再停 1.5 秒 | f24 主水管 1 → 6,母亲换水管再清出 3 格底层(沉了 23 轮);f25 主水管 6 → 12;f26 猫看病、Ture 发烧清出 8 格,主水管 12 → 24、FTPS 计时 +5、后刹 +2,19 道新外框一格格画出,英雄数字数到 19 / 63;平账后主水管的地盘描一道墨边;侧栏“开着的问题”加上「主水管:挖开换还是免挖? 8/17 10:00 前 · 去回答 →」 | 腾出来的位置归下一件急事,它从一圈数起 | 动:清出、加圈、新框、分子计数。落定:分母、守恒、底层 6、名次表 |
讲完后页面就是普通的复盘工具,停在 f26(地址栏 frame=26),主水管的墨边留到第一次交互为止。
一轮怎样走。 全速 3.2 秒(半速是每个时刻 × 0.5,第一个动作提前到 0.2 秒;快放 0.32 秒):
- 0 切点:顶栏日期、帧号与旁白一帧硬切;侧栏换成这一轮的账,分子写 0、分母是上一轮的数(灰),注“本轮还没平账”。
- 0.40 第一个动作:清出。被清出的格一格格清空(每格 0.24 秒:圈向中心收、淡出,浅底一起退),原位留一道灰框 0.5 秒散去;侧栏“让出”逐格计数。没有清出的轮跳过这一步,后面整体提前 0.55 秒,轮长不变。
- 0.95 加圈:留下的每一格在最里面多一圈(从外侧 1.5 px 收进来、淡入,0.28 秒;64 格从左上到右下错开 0.12 秒);圈满 12 圈又从第一轮起的,这一刻涂实。
- 1.50–2.20 新框:认领的空格浅底淡入,最外一道框沿顺时针画出(每格 0.24 秒,相邻错开不超过 25 ms,格多时压缩);分子随每一格画完加一。
- 2.25 平账:分母一帧硬切,守恒的等号由灰转墨,“从第一轮起”与图下的驻龄分布硬切,名次表换成这一轮,侧栏的问题列表一帧切换。概念层回到零。
- 2.25–3.20 静止,留着读;3.20 秒切到下一轮。
逐帧(←/→)用短版 0.9 秒:没有 0.4 秒空白;清出 0.05–0.29 秒,加圈 0.25–0.53 秒,新框 0.45–0.75 秒,0.80 秒平账,数字硬切、不逐格计数。按住方向键或一次跳多帧时直接到终态;往回退直接到那一帧的终态,不倒放(倒放会像把份额要了回来)。空格连播用半速,28 轮约 45 秒。
两层。 信息层(九成以上):64 格的圈、实心、虚线留白与浅底地盘,名次号,图下的驻龄分布,名次表,侧栏的英雄数字、守恒、清单与问题,讲完后的墨边。只用墨黑与灰;只在节拍上落定。概念层(一成以内,静止时为零):被清出的格原位留下的灰框。只用灰,不带字与数;个数 = 这一轮清出的份数。规矩:概念层不进侧栏与名次表;一份没清出的轮(composite 的 f11、f17、f27)不出现任何灰框。
动起来之前的约束
- 绝不动:64 格的位置;一格只在份额易手时换主人;圈数只表示驻龄;颜色只有墨与灰(不用身份色,也不用朱红:朱红在别的形式里只给“你”,这里没有要标的“你”,问题的开闭写在侧栏与卡里)。
- 每格每轮至多三个动作:清出、加圈、新框,各一次;平账后不再动。
- 不画配对:清出与新框分两步走,中间不连线;卡与侧栏写“同一轮清出 / 新给”。
- 驻龄与英雄数字只读回放现算:
records、currentAsk、askLife;故事的帧号是页面常量;不改 scenario、shared、engine 与演示数据,不加字段。 prefers-reduced-motion:每轮直接到终态,不画灰框;直接打开时不讲,直接停在故事的终态。- 性能:64 格画在一张 canvas 上,静止时不重绘;一帧整图重绘要在 2 ms 以内。
- 合页顶栏选帧(
followFrames):立即停止讲述、收起卡,按逐帧短版走到那一帧(跨多帧直接到终态)。
手写情境(story)的三拍。 照同一张时间表,共约 21.7 秒,停在 f19:拍 1 是 cold-01(半速,发布的 6 道外框)到 f14 快放,序号 13(f11)发布底下 3 份涂实、多停 0.5 秒,f14 多停 0.6 秒;拍 2 是 f15 全速再停 0.8 秒:试用、发布清出 27 格(试用里 12 格待了 14 轮),妈妈新给 12 格,“让出 27 = 新给 12 + 退回留白 15”;拍 3 是 f16–f18 半速(f17、f18 一份没新给,只多一圈),f19 全速再停 1.5 秒:妈妈清出 18 格,发布、试用、苏禾、链路画出 21 道新框,英雄数字 21 / 55,“让出 18 + 留白减 3 = 新给 21”,从第一轮起 3 份(发布);链“去回答 →”的是「周三演示前,开放范围怎么定?」(?variant=d&frame=21)。讲完描边的是发布。
4 交互
与让位图、小倍数同一套标准,悬停卡、弹出、引线、详情卡的时间与样式取共用模块 motion/ix.mjs,本页只提供几何。悬停与展开都只读同一份回放,都让讲述与连播停在当前时刻;收起后 0.4 秒接着走。
- 什么算一件。 此刻根层的一件事(条目 id),加上留白(
free)。方阵里一格认它的主人;空格认留白;名次表的一行认它那件事。命中按格算:格缝归最近的格,方阵四边外放宽 12 px。 - 悬停
- 触发:鼠标移到格或名次表的一行;Tab 到它(每件一个视觉上隐藏的按钮,按名次排,留白在最后);触屏轻点一下就是悬停。
- 出什么:概要卡(纸底、墨框、等宽字),挂在被指的那一格上方,放不下时翻到下方。内容:短名与这一帧的条目标题;“24 / 64 份 · 这一轮 12 → 24(+12)”;格龄一行(“12 份 1 轮 · 6 份 2 轮 · 5 份 3 轮 · 1 份 11 轮”);最老的一份从哪一轮起(“最老一份 f16 起,11 轮”或“从第一轮起”);它此刻开着的问题与期限。留白的卡写“留白 1 / 64 · 这一轮 12 → 1”。
- 其余怎么变:这件事的地盘描 1.6 px 墨边并保持原亮度,名次表里它那一行加粗;其余格降到七成。
- 节拍:停在轮中途时卡守这一轮的节拍,平账前写“本轮还没平账”,不提前说出后值。
- 收回:离开 0.15 秒宽限,卡 0.12 秒淡出,再过 0.4 秒接着播。
- 展开
- 触发:点格、名次表的一行或概要卡;聚焦后按 Enter 或空格。
- 动什么:0 秒是切点,顶栏旁白改写成“这一件 · f26 · 8/16 周日 00:00 / 主水管”,侧栏切到“这一件”。0–0.3 秒,这件事的格整体沿“方阵中心 → 地盘重心”的方向弹出 8 px(重心离中心不到一格时向右,朝着卡;ease-out,过冲 3.7%),其余降到 18%。0.4–0.6 秒,引线从地盘最右的一格出来,先进它右边的格缝,再沿最近的一条横缝走到方阵右沿外 12 px,最后接到卡。0.6–0.9 秒,详情卡从引线末端展开(先沿接上的那条边、再向外),放在名次表那一栏上,不盖方阵。
- 详情卡(这一轮这一件):时间、帧号与“新给 / 清出 / 不变”;短名与 前 → 后;条目标题;格龄的圈形小样与份数;最老的一份从哪一轮起;这一轮清出的几格各待了几轮(“清出 6 格:最老 8 轮”);同一轮的另一边(“同一轮新给:主水管 12 · FTPS 计时 5 · 后刹 2”,注“回执只记增减,不记谁让给谁”);这一轮你的回应、这一轮答掉或失效的问题;此刻开着的问题与期限,旁边“去回答 →”;摘要。不承载作答。
- 面板“这一件”(两周这一件):份额逐轮小条(每轮一根,实心是留下的、空心框是这一轮新给的,这一轮亮、之后的淡);沉得最久的一份与它从哪一轮起;份额变动的那几轮(“f23 24 → 6 · 清出 18 格,最老 22 轮”,点时间跳到那一帧);问题关上的那几轮;最后是摘要、依据、来源与版本(
stage.mjs的itemDetailHTML,折叠)。留白的“这一件”是逐轮留白小条与动用、退回的轮次。 - “去回答 →”:只给此刻开着的问题(
askLife[当前帧].open,含子层),链到同一帧的 D 手机首页../../index.html?variant=d&frame=<当前序号>,经withDataset带上?data=。已关上的问题只写怎么关上(答掉写你的动作句,失效写“随改写失效:母亲换水管改为已办”),不给链接。 - 收回:Esc、点空白、再点同一件:卡 0.2 秒收起,引线 0.15 秒缩回,格 0.2 秒落回,然后一帧切回这一轮的旁白与侧栏。点另一件先收回再展开那一件。←/→、选帧、播放键与合页顶栏选帧立即收起并照常执行。
prefers-reduced-motion时直接到终态。
- 键盘与触屏。 Tab 逐件看概要,Enter 展开,Esc 收回且焦点回到那一件;←/→ 逐帧,空格连播,Home / End 到头尾;触屏第一下是悬停,第二下展开。
- 脚本。
window.foldAge给出与foldYield同名同义的hover / unhover / open / close / ixAt / paused / pointOf / ixState,名字可用条目 id 或短名;另有nearOf(name, d)(这件事贴着方阵外沿那一格外 d px 的点)、pick()、tell / skip / tellAt(秒) / play / stop / telling / playing / settled / phase、plan、slots(i)(第 i 帧每格的主人与驻龄)、perf / resetPerf。window.foldSketch照旧。
和让位图不同的地方:让位图一件事在 28 行里反复出现,靠描边追线;这里一件事此刻只有一块地盘(偶尔碎成几块),弹出沿方阵中心到它的重心,引线沿格缝走。和小倍数不同:小倍数的格按名次每轮重排,这里的格两周不挪。
5 实现与出入
文件。 sketches/age.html、age.mjs、age.css;页框用 stage.mjs(顶栏、刻度条、侧栏、图例、键盘),交互接 motion/ix.mjs。注册:catalog.mjs 的 sketches、合页 all.html 的“第四轮:关系的画法”一组、体验站 prototypes.mjs、scripts/check-ix.mjs 与 scripts/shoot-forms.mjs。不改 scenario、shared、engine 与演示数据。
画法。 64 格画在一张 canvas 上(1440×900 下格距 78 px、格边 68 px、缝 10 px),自下而上:地盘的浅底(同一主人的相邻格跨缝连起来)、地盘之间的界线、清出的灰框(概念层)、虚线留白、同心方框(1.1 px 墨线,每圈向内收 2.45 px,画到 12 圈)、实心、名次号(地盘阅读顺序第一格左上的格缝里)。格上方的一句说明、图下的驻龄分布与右边的名次表是叠在图上的 DOM,只在平账时换。静止时 canvas 不重绘;悬停、展开的压暗与弹出按 ix.frame() 每帧重画,整图一次 0.2–0.4 ms。
格位与驻龄。 页面载入时按第 2 节的分法把 28 轮(story 36 轮)的格位一次算好(slots),每轮记下清出与认领的格;驻龄就是格位表里的年龄,与第 2 节“取最小值”的规则逐格一致(node 核对两套数据,0 处不符;页面里 foldAge.slots(i) 可查)。英雄数字、守恒、驻龄分布与名次表都从同一张表数出来,所以“新给”永远等于方阵里只剩一道框的格数。
时间表。 全速、半速、快放、短版四张表(FULL / timing('half') / QUICK / SHORT),轮长 3.2 / 1.6 / 0.32 / 0.9 秒;没有清出的轮整体提前 0.55 秒(半速 0.275 秒)。讲一遍、连播与逐帧都走同一个 post:页面自己推进时(讲一遍、连播)由 drive() 指定速度,人推进时(←/→、刻度条、合页选帧)一步用短版、跨多步或按住方向键直接到终态。悬停或展开时 ix 停住这只表,收起 0.4 秒后接着走。
与第 3、4 节的出入。
- 地盘之间多了一道细线。 第 1 节只打算靠浅底与纸色缝分出街区。画出来浅底(纸色 L 0.975 对浅底 0.93)在 14 件事挤满 64 格时太弱,相邻两件分不清。于是在两件事之间、事与留白之间以及方阵外沿的格缝中线加一道 1 px 灰线(ink-3 的 55%),像地图的界线;同一件事的格之间没有线。它属于信息层,随浅底一起淡入淡出。
- 名次表的格龄条用竖刻度,不用小方块。 一份一根 3 px 宽的刻度,高 = 待了几轮(画到 12 轮),从第一轮起的那几份更高一截、到 14 px。小方块在一行 24 份时挤不下,又只能用深浅冒充圈数;刻度高低与方阵里的圈数是同一个量。件少时一行两层(名字与一句话,下面是刻度),件多(composite f17 有 26 件)时一行一层,那句话放进悬停提示。
- 半速不是严格减半。 每格的清出与新框 0.14 秒、加圈 0.18 秒(减半是 0.12 与 0.14 秒),否则快到看不清一格;轮长仍是 1.6 秒。
- 守恒式里留白那一项平账时才出现。 “留白减 11”“退回留白 25”平账前写“…”,和由灰转墨的等号一起落定:它是这一轮的余额,提前写出来就说出了后值。
- story 讲一遍是 21.4 秒,不是第 3 节估的 21.7 秒(节拍照写,估计时多加了一个快放轮);composite 是 23.0 秒,与估计相同。
- 灰框比第 1 节写的 16% 深一些(ink-3 的 55%,约合墨色的四分之一):16% 时在半速 0.25 秒里看不出来。
- 默认帧写进地址。 不带
?frame=打开时,页面在建页框之前把故事的终帧写进地址(composite f26、story f19;顶层直接打开先写f00再讲)。画廊缩略图与体验站的弹框都是 iframe,于是也停在这一帧。画廊卡片上的“默认帧”取catalog.mjs的frame: 'f26'(一页只有一个字段),所以 story 下卡片写 f26、页面停在 f19,没有为此改共用的画廊。 - 讲完描边的那一件是算出来的:最后一轮认领格最多的那件(composite 主水管,story 发布),不是写死的名字。
- 详情卡“最老的一份” 小标题右边写轮数,正文写“从第一轮(8/3 周一 12:00)起,一直在这件事上”,不重复轮数;概要卡仍写“f16(8/11 周二 00:00)起,11 轮”。
6 验证
2026-10-28,本机自起的静态服务(不用 4173),无头 Chrome,1440×900。下面各项先在 main 的 4b42461(#34 变形)上跑全;main 合入 #35 峰值保持(b480277)后再 rebase 一次,重跑 make check 与两套数据的 check-ix(本页、峰值保持、D 手机首页),都通过。
make check通过。- 交互(
scripts/check-ix.mjs,两套数据、全部页面)。 composite:本页 16/16,变形 16/16,其余页都过;批次记谱在整套里有一项(“Esc 收回”)采样时卡还没收完,单独重跑 16/16。story:本页 16/16,其余页都过;D 手机首页“轻点卡:0.48 秒后详情到全屏(pilot)”一项在 4b42461 上两次都是 22/23,到 b480277 上重跑 23/23,是当时本机负载下的时序(D 加载的文件本分支一个没改)。b480277 上:本页、峰值保持两套数据都是 16/16,D 手机首页两套都是 23/23。 - 截图与报错(
scripts/shoot-forms.mjs --only age,all --gpu --check all-sync)。 composite 与 story 都无报错;合页逐节报告所在帧、跟着顶栏选帧,驻龄一节也在。scripts/export-all.mjs导出的单文件在file://下 all-sync 同样通过。 - 体验站(
npm run site:build) 通过,产物里有本页与预览图site/previews/age.webp(按 docs/development.md 的参数,取 f26)。 - 帧率(
--gpu,?perf,foldAge.perf()):讲一遍 23.5 秒 1409 帧,平均 60 fps,p95 帧间隔 16.7 ms,没有超过 20 ms 的帧;连播 15 秒 899 帧,60 fps;逐帧短版 60 fps。整图一次重绘平均 0.16 ms、p95 0.3 ms;静止时不重绘。切帧(脚本加样式与布局)p50 1.6 ms、p95 2 ms。同机另一遍讲述测到过一帧 150 ms,是并行任务的负载,重测不复现。 - 数字对账。 页面里 composite f26 是 19 / 63、从第一轮起 6;f23 是 5 / 39、清出 30(最老 22 轮,其中 6 格从第一轮起);story f19 是 21 / 55、从第一轮起 3:与第 2 节一致。格位表的年龄与“取最小值”的驻龄规则逐格一致。
- 讲一遍与静止。 顶层直接打开:composite 23.0 秒停在 f26、描边主水管,story 21.4 秒停在 f19、描边发布,任意键跳到终态;带
?frame=、画廊与合页的 iframe、体验站弹框都静止在默认帧;prefers-reduced-motion时直接停在故事终态,逐帧也直接到终态、不画灰框。 - 静帧(轮中途 f23 清出与 f26 新框、悬停、展开到 0.9 秒,另有 story 的清出与展开)只在本机,不提交。
还没做:没有在 Safari 里看过;子层的驻龄、撤下又恢复的“回来”都没有画(见 CONCEPTS.md 卡片“还差什么”)。