Harden \stgroup and tighten the callout budget (review follow-up)

- Bracket ink is part of the fit: \st@facebody drops -inkw/-inke extreme
  coordinates and \stface/\ststack register them with the enclosing
  group/col/row fit, so a group outline can no longer be crossed by a
  member's bracket arms
- \stlink inside \stgroup or \stcol is now a package error: sub-flow
  members never terminate a pending connector, so the arrow was dropped
  silently while the label still rendered
- \stgroup requires role= (explicit role=neutral for mixed groups) and
  must bind at least two members or one \stcol partition; a lone stack
  or face inside a group is a dirty-build warning
- lint: default budget is one \stcallout per figure; the
  allow-multiple-callouts directive relaxes it to one per band
- build.sh: clean-build hint no longer names hue budget (lint owns it)
- tests/group-callout.tex reworked: multi-member group with a bracketed
  member as a regression probe, single callout; new negative fixtures
  group-link, group-norole, group-single, callout-budget
- api.md, checklist.md, style.md, layout.md, SKILL.md updated to match
This commit is contained in:
dela
2026-08-05 17:03:14 +08:00
parent 866173a831
commit de917a2fbd
13 changed files with 181 additions and 54 deletions
+18 -26
View File
@@ -1,7 +1,9 @@
% Regression test for \stgroup and \stcallout.
% - a group over one stack (the three sheets are one composite object),
% - a group over two adjacent shards (the pair is one partitioned parent),
% - a side card hanging off the right edge of each band.
% - a group over three adjacent head faces (the trio is one composite q);
% the first face carries bracket=true, probing that bracket ink is part
% of the group's fit -- the outline must clear the bracket arms,
% - a group over a \stcol partition (two shards tiling one parent W),
% - one side card for the whole figure, hanging off a finished band.
% No absolute coordinate appears anywhere in this figure.
% ../scripts/build.sh group-callout.tex
\documentclass[border=10pt]{standalone}
@@ -9,9 +11,7 @@
\stsetrole{act}{stTeal}
\stsetrole{w}{stOrange}
\stsetrole{q}{stTeal}
\stsetrole{k}{stViolet}
\stsetrole{v}{stCoral}
\stsetrole{q}{stViolet}
\stdim{T}{7}
\stdim{d}{5}
@@ -20,27 +20,18 @@
\begin{document}
\begin{tikzpicture}
\ststage{S1}{一、分组框:把已经排好的面命名为一个复合对象}
\ststage{S1}{一、分组框:三个相邻的头合起来是一个复合对象}
\strow{R1}{T}
\stface[role=act, bracket=true]{X}{}{T}{d}
\stlink{l1}{三路投影}
\stlink{l1}{按头切分}
\stgroup[role=q]{QG}
\ststack[role=q]{Q}{}{T}{dh}{3}
\stgroupend
\stgroup[role=k]{KG}
\ststack[role=k]{K}{}{T}{dh}{3}
\stgroupend
\stgroup[role=v]{VG}
\ststack[role=v]{V}{}{T}{dh}{3}
\stface[role=q, bracket=true]{Q1}{}{T}{dh}
\stface[role=q, gap=2mm]{Q2}{}{T}{dh}
\stface[role=q, gap=2mm]{Q3}{}{T}{dh}
\stgroupend
\strowend
\stcaption{X}{$\mathbf X$}{$T\times d$}
\stcaption{QG}{$\mathbf q$}{$h\times T\times d_h$}
\stcaption{KG}{$\mathbf k$}{$h\times T\times d_h$}
\stcaption{VG}{$\mathbf v$}{$h\times T\times d_h$}
\stcallout{N1}{4.2cm}{R1}{分组框在说什么}%
{外框与里面的面同色,因为它圈的是同一个对象的多头视图,不是一个新张量。
框的内边距计入行的包围盒,所以下面的符号轨仍然从框底起算。}
\ststage{S2}{二、分组框圈住两片相邻的分片,标注它们合起来是谁}
\strow{R2}{T}
@@ -58,8 +49,10 @@
\stcaption{H}{$\mathbf H$}{$T\times d$}
\stcaption{WG}{$\mathbf W$}{$d\times T$}
\stcaption{Z}{$\mathbf Z$}{$T\times T$}
\stcallout{N2}{4.2cm}{R2}{一行一张卡}%
{同一个 band 上挂两张卡会被 lint 拒绝:并排的说明卡是仪表盘,不是图。}
\stcallout{N1}{4.2cm}{R2}{分组框在说什么}%
{外框与成员同色,因为它圈的是同一个对象的分片视图,不是一个新张量。
括号等装饰墨迹也计入外框的包围盒,所以框永远不会被成员的括号穿过;
整张图默认只允许一张这样的卡片,再多就是仪表盘。}
\sttopformula{F}{$\displaystyle \mathbf Z=\mathbf H\,\mathbf W,\qquad
\mathbf W=\begin{bmatrix}\mathbf W_a\\ \mathbf W_b\end{bmatrix}$}
@@ -69,10 +62,9 @@
{$T$ 序列长,$d$ 模型维,$d_h$ 头宽,$h$ 头数}
{}
{分组框是横向的子流:成员写在块里,所以它只能圈住相邻的对象;
两侧的内边距各占一份宽度,箭头也接在外框上而不是成员上——
否则连接线会穿过一个并非自己端点的框。
说明卡挂在 band 右侧,并把纵向游标压到自己底边以下,所以卡片过高只会撑开空白,
不会压住下一段。}
它必须绑定至少两个成员或一个 \texttt{stcol} 分片,单个对象外再画框是装饰。
连接线不能在框内起止——\texttt{stlink} 写在组里是构建错误,
闭合后箭头接在外框上,而不是穿过一个并非自己端点的边。}
\end{tikzpicture}
\end{document}
+16
View File
@@ -0,0 +1,16 @@
% \stlink inside a group: members never terminate a pending connector, so the
% arrow would be dropped silently. The package must error rather than build a
% figure whose label appears but whose arrow does not.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup[role=x]{G}
\stface[role=x]{A}{}{T}{T}
\stlink{l1}{lost arrow}
\stface[role=x]{B}{}{T}{T}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+14
View File
@@ -0,0 +1,14 @@
% A group with no role would silently fall back to neutral gray, violating
% the "outline in the composite's own hue" rule. The package must error.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup{G}
\stface[role=x]{A}{}{T}{T}
\stface[role=x]{B}{}{T}{T}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+13
View File
@@ -0,0 +1,13 @@
% A group around a single object is decoration: the stack already reads as
% one composite. The package must reject this with a dirty-build warning.
\documentclass[border=2pt]{standalone}
\usepackage[en]{supertensor}
\stsetrole{x}{stTeal}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stgroup[role=x]{G}
\ststack[role=x]{Q}{}{T}{T}{3}
\stgroupend
\strowend
\end{tikzpicture}\end{document}
+17
View File
@@ -0,0 +1,17 @@
% One card per band, but a card on EVERY band, is still a dashboard. The
% default budget is one callout per figure; this must fail lint without the
% allow-multiple-callouts exemption.
\documentclass{standalone}
\usepackage[en]{supertensor}
\stdim{T}{4}
\begin{document}\begin{tikzpicture}
\strow{R1}{T}
\stface{A}{}{T}{T}
\strowend
\stcallout{N1}{3cm}{R1}{first}{one per figure}
\strow{R2}{T}
\stface{B}{}{T}{T}
\strowend
\stcallout{N2}{3cm}{R2}{second}{this one belongs in the meaning box}
\sttopformula{F}{$A,B$}
\end{tikzpicture}\end{document}