Conversation
… `\bmod`'s `\nonscript`
Three measured math residuals, each left behind by an earlier lane.
## 1. `\lvert`/`\rvert`/`\lVert`/`\rVert` were `\mathrel`
`symbol_class` is keyed by the glyph, so the four amsmath spellings
inherited the class of the characters they share: `\mid`'s U+2223 (Rel)
and `\|`'s U+2016 (Ord). amsmath declares them open and close fences
(`amsmath.sty` 182-209) by taking `\vert`/`\Vert`'s delimiter code and
adding `"4000000` — class nibble 4, `\mathopen` — then another
`"1000000` for 5, `\mathclose`; its fallback branch writes the same as
`\DeclareMathDelimiter{\lvert}{\mathopen}{symbols}{"6A}...`. They now
force their class on the atom, as `\bot` already does over `\perp`'s
glyph.
Measured with pdfTeX 3.141592653-2.6-1.40.27 (TeX Live 2025) at 10pt
under amsmath, `\showthe\wd` of `\hbox{$...$}`, against `$abc$`
13.90510pt and `$a=b$` 22.91077pt. FlashTeX's own column is at 18pt,
where 1mu is exactly 1pt, so the two columns agree in mu and not in
glyph width:
| | pdflatex | mu | before | after |
| --- | --- | --- | --- | --- |
| `$a\lvert b\rvert c$` | 19.46068 | +0mu | 56.00000 | **36.00000** |
| `$a\lVert b\rVert c$` | 23.90514 | +0mu | 40.32000 | 40.32000 |
| `$a=\rvert b$` | 22.91084 | -5mu | 43.15600 | **38.15600** |
| `$a=\rVert b$` | 25.13307 | -5mu | 45.31600 | **40.31600** |
| `$a\mid b\mid c$` | 30.57152 | +20mu | 56.00000 | 56.00000 |
so every `\lvert x\rvert` was paying the full 20mu of the `\mid` row —
5mu on each of four boundaries — and a closing bar after a relation was
5mu wide where `Rel`→`Close` is 0mu. `\lVert`/`\rVert` were Ord rather
than Rel, which coincides with Open/Close between two Ords; the
`$a=\rVert b$` row is where it does not. `\mid` itself is untouched,
and `$a\left|b\right|c$` is a third box again (22.79393pt) because
`\left`/`\right` build an Inner atom, so it was never a substitute.
## 2. Bare `$\Vert$` and `$\vert$` did not parse
Both resolved only as fence names inside `\left`/`\right`/`\big`
(`DELIMITER_COMMANDS`), so `$\Vert$` answered "\Vert is not supported in
math mode" and typeset the literal text while `$\|$` — the same symbol,
the same `\DeclareMathDelimiter{..}{\mathord}{symbols}{"6B}` at
`fontmath.ltx` 465-468 — worked. Both are `\mathord` in the kernel, not
aliases of the amsmath pair, and they join the arm above with that
class: `\Vert` is U+2016 at 7.16400pt (`\lVert`'s box), `\vert` is
`\mid`'s U+2223 at 5.00400pt, both previously diagnostics.
`\left\vert`/`\left\Vert` keep spelling the same cmsy `"6A`/`"6B` slots
as `|`/U+2016; `cm::delimiter_slot` maps `|` and U+2223 to one slot, so
the two spellings are one delimiter downstream.
## 3. `\bmod` kept 1mu where pdflatex keeps 5mu in script styles
`\bmod` is `\nonscript\mskip-\medmuskip\mkern5mu\mathbin{mod}\penalty900
\mkern5mu\nonscript\mskip-\medmuskip` (`latex.ltx` 15556-15559). The
`\mkern5mu` is unconditional; the `\mskip-\medmuskip` sits behind a
`\nonscript` and so fires in text and display style only — exactly where
the Bin class contributes its own 4mu. The two cancel whenever the Bin
survives, so the total is 5mu a side in *every* style, and a boundary
that degrades the Bin to Ord leaves 1mu in text but the whole 5mu in
script. The previous arm folded that into one constant 1mu kern, which
is right in text and 4mu short a side in script.
Measured at 10pt with `\mathrm{mod}` as the control, 1mu being
0.555542pt in text, 0.455246pt in script and 0.408950pt in
scriptscript (quads of 10, 8.19443 and 7.36111pt):
| | `\bmod` | control | difference |
| --- | --- | --- | --- |
| `$a\bmod b$` | 34.29970 | 28.74428 | 10mu |
| `$\scriptstyle a\bmod b$` | 27.59433 | 23.04187 | 10mu |
| `$\scriptscriptstyle a\bmod b$` | 23.94104 | 19.85153 | 10mu |
| `$\bmod b$` | 24.56947 | 23.45839 | 2mu |
| `$\scriptstyle\bmod b$` | 23.25668 | 18.70422 | 10mu |
| `$\scriptscriptstyle\bmod b$` | 20.06890 | 15.97939 | 10mu |
| `$a\bmod$` | 25.56370 | 24.45262 | 2mu |
| `$\scriptstyle a\bmod$` | 24.07767 | 19.52520 | 10mu |
`Nucleus::Space` grows a `nonscript` flag for this — glue that vanishes
at `level > 0`, the same test the class-spacing table already uses for
its text-styles-only rows — and `\bmod` emits the definition's five
nodes in its own order instead of one folded kern. In FlashTeX at 18pt,
`$x^{a\bmod b}$` goes 44.39480 → **49.99480** and `$x^{\bmod b}$`
38.09480 → **43.69480**, both +5.6pt = 8mu at the script size; text
style is unchanged at 60.00400.
## Tests and gates
Two tests in `package_gating_tests`:
`the_vert_family_is_open_close_and_ord_but_never_rel` derives the bar
widths from the lone glyphs and pins all six spellings plus the `\mid`
control, and `bmod_keeps_the_full_five_mu_a_side_in_script_styles`
measures inside a superscript under all three package combinations. The
stale note in `double_bar_tests` saying `\Vert` is a fence name only is
now an assertion that it parses.
`crates/compiler`: 296 lib tests passed, 0 failed (294 on this same
base — the 2 new); every integration target passes except
`tests/robustness.rs::deeply_nested_input_does_not_blow_the_stack`,
which overflows its stack identically on the unmodified base and is not
this change. `crates/math-layout`: 66 passed, 0 failed, untouched.
The six names move from generated `math_symbol` rows to `math_structure`
rows because they now carry a class, and `\vert`/`\Vert` are genuinely
new, so `--supported` goes 542 → 544 math entries;
`docs/user/compiler.md` and `supported/supported-latex.json` are
regenerated by `crates/compiler/scripts/render_supported_latex.sh`,
which `tests/supported_latex.rs` gates. No oracle data was regenerated.
`crates/render-pipeline` builds against `vendor/compiler`, so all of
this is inert in the pipeline until that pin moves; no vendor copy is
touched here and the re-pin is left to the integration lane.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xd5Hmwh5GHNTiAmHUJ1MZu
Implementation-Agent: engine-engineer (math residuals lane, linux-primary)
Commit-Executor: Claude Code direct (Cursor usage limit fallback)
GoKubar
added a commit
that referenced
this pull request
Sep 21, 2026
) #856 stopped math mode typesetting an unresolved control sequence's name; what it left is the dropped command itself. Re-measured on main (13285fe) with `tools/real-world-corpus/math_leaks.py` over the public documents of #846's table (Green–Tao amsart, the Horodecki revtex4 review, the transformer paper, amsldoc, tufte, three Evan Chen handouts with today's evan.sty): the PDF-text leak of names is 0 everywhere, and the `\X is not supported in math mode` count per name is 379 \varrho, 228 \cal, 203 \bf, 192 \rm, 56 \ensuremath, 50 \Vert, 10 \mkern, 6 \pmb, 4 \framebox, 4 \columnwidth, 3 \backslash, 2 \bigvee, 2 \smash, 2 \hdotsfor, 2 \varliminf, 1 each of \coprod, \bigotimes, \bigoplus, \bullet, \prec, \longmapsto, \kern, \vert, \negmedspace, \negthickspace, \leftroot, \uproot, \injlim, \projlim, \varinjlim, \varprojlim, \varlimsup, \iiiint, \idotsint, \varGamma..\varOmega (11), \it (2) evan.sty's `\dang`/`\half`/`\dg` rows of the original table are gone since .sty reading landed; `\dagger` since #441. Implemented, each per fontmath.ltx / latex.ltx / amsmath.sty (TeX Live 2026), line numbers in the arms: * `COMMAND_GLYPHS` rows for the kernel symbols math-layout already boxes from their cmmi/cmsy/cmex slots (9bdcd5c) but the compiler never named: \varrho, \bullet, \prec, \succ, \preceq, \succeq, \coprod, \bigvee, \bigwedge, \biguplus, \bigcap, \bigcup, \bigotimes, \bigoplus, \bigodot, \bigsqcup, \longmapsto — with their classes in `symbol_class`, display limits in `takes_display_limits`, and Latin Modern Math advances (read from the bundled latinmodern-math.otf) for the base-14 export route. * `\backslash`: Ord over the \setminus glyph (cmsy "6E). The six `vert` spellings with forced Open/Close/Ord classes — the arm first written for #298, which never landed. * `\ensuremath` in math (the group) and in text (inline math over the braced argument, `Parser::ensuremath`). * `\mkern`/`\mskip` mu glue (`take_mu_glue`); amsmath's `\medspace`, `\thickspace`, `\negmedspace`, `\negthickspace` (gated); `\thinspace`/ `\negthinspace` (kernel .16667em text kern, 3mu under amsmath); `\hdots` (amsmath alias of \ldots). * `\rm \bf \it \sf \tt \cal \mit` in math as `\DeclareOldFontCommand`'s math branch (`\@fontswitch`, latex.ltx 14312): the rest of the current group re-read as `\mathrm{...}` etc. (`rest_of_group_alphabet`; the group ends at `}`, an enclosing `\right`, `&` or `\\`). `{\bf 1}_S`, `${\cal H}_A$` and `\inf_{\sigma\in\rm SEP}` all come out as pdflatex sets them. * `missing_package` recovery drops the command instead of typesetting its literal name, as pdflatex and #856 do. * `\mathbf` with a non-plain argument parses it as math instead of keeping a control word's *name* as bold text (`\mathbf{\alpha}` printed `\alpha`; the revtex idiom `\bf\sigma` surfaced it): lowercase Greek passes through unchanged as in pdflatex, and letters/digits that pdflatex would embolden are reported as not set bold. * Generated inventory (`render_supported_latex.sh`) regenerated. * `tools/real-world-corpus/math_leaks.py`: the re-measurement harness (pdflatex is the oracle only; never in the product path). Verified: `cargo test` and `cargo test --release` in crates/compiler, 0 failures (both profiles, --no-fail-fast; 528 unit tests, with new ones for the group boundary of `\rm`/`\bf`/`\cal` and `\mkern`'s mu glue). Oracle fixtures for glyph identity and positions land with the render-pipeline re-pin. Left out, with root causes: \pmb (amsbsy triple overprint), \smash, \framebox/\parbox/\columnwidth in math, \kern<dimen>, \leftroot/\uproot, \hdotsfor (unimplemented constructs, 1–6 uses, all in amsldoc); \varGamma..\varOmega and the \varliminf/\injlim family (amsmath declarations needing cmmi italic capitals and \mathop{\underline{\lim}} builds, 1 use each); \xymatrix/\ar (xypic, unsupported package). Implementation-Agent: Claude Fable 5.1 (kabir-claude GH846, mac-m5pro-kabir) Commit-Executor: Claude Fable 5.1 via Claude Code (direct git) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xd5Hmwh5GHNTiAmHUJ1MZu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three measured math residuals in
crates/compiler, each left behind by a lane that ran out of scope. All three were verified against pdfTeX 3.141592653-2.6-1.40.27 (TeX Live 2025) with\showthe\wdof\hbox{$...$}before anything was written down.FlashTeX's own numbers below are taken at 18pt, where 1mu is exactly 1pt, so the two columns agree in mu rather than in glyph width.
1.
\lvert/\rvert/\lVert/\rVertwere classedRelsymbol_classis keyed by the glyph, so the four amsmath spellings inherited the class of the characters they share —\mid's U+2223 (Rel) and\|'s U+2016 (Ord). amsmath declares them\mathopen/\mathclose(amsmath.sty182-209). They now force their class on the atom, exactly as\botalready does over\perp's glyph.$a\lvert b\rvert c$$a\lVert b\rVert c$$a=\rvert b$$a=\rVert b$$a\mid b\mid c$Controls:
$abc$13.90510pt,$a=b$22.91077pt.Every
\lvert x\rvertwas paying the full 20mu of the\midrow.\lVert/\rVertwere Ord, which coincides with Open/Close between two Ords — the$a=\rVert b$row is where it does not, and it was 5mu wide.\midis untouched, and$a\left|b\right|c$is a third box again (22.79393pt) because\left/\rightbuild an Inner atom, so it was never a substitute for either.2. Bare
$\Vert$and$\vert$did not parseBoth resolved only as fence names inside
\left/\right/\big, so$\Vert$answered "\Vert is not supported in math mode" and typeset the literal text, while$\|$— the same symbol, the same\DeclareMathDelimiter{..}{\mathord}{symbols}{"6B}atfontmath.ltx465-468 — worked. Both are\mathordin the kernel, so they join the arm above with that class:\Vert→ U+2016 at 7.16400pt (\lVert's box),\vert→\mid's U+2223 at 5.00400pt. Both were diagnostics before.3.
\bmodkept 1mu where pdflatex keeps 5mu in script styles\bmodis\nonscript\mskip-\medmuskip\mkern5mu\mathbin{mod}\penalty900\mkern5mu\nonscript\mskip-\medmuskip(latex.ltx15556-15559). The\mkern5muis unconditional; the\mskip-\medmuskipsits behind a\nonscriptand fires in text and display style only — exactly where the Bin class contributes its own 4mu. The two cancel whenever the Bin survives, so the total is 5mu a side in every style, and a boundary that degrades the Bin to Ord leaves 1mu in text but the whole 5mu in script. The old arm folded that into one constant 1mu kern: right in text, 4mu short a side in script.Against
\mathrm{mod}at 10pt (1mu = 0.555542pt text, 0.455246pt script, 0.408950pt scriptscript):\bmod$a\bmod b$$\scriptstyle a\bmod b$$\scriptscriptstyle a\bmod b$$\bmod b$$\scriptstyle\bmod b$$a\bmod$$\scriptstyle a\bmod$Nucleus::Spacegrows anonscriptflag — glue that vanishes atlevel > 0, the same test the class-spacing table already uses for its text-styles-only rows — and\bmodemits the definition's five nodes in its own order. In FlashTeX at 18pt,$x^{a\bmod b}$goes 44.39480 → 49.99480 and$x^{\bmod b}$38.09480 → 43.69480, both +5.6pt = 8mu at the script size; text style is unchanged at 60.00400.Tests
Two tests in
package_gating_tests.the_vert_family_is_open_close_and_ord_but_never_relderives the bar widths from the lone glyphs and pins all six spellings against the\midcontrol;bmod_keeps_the_full_five_mu_a_side_in_script_stylesmeasures inside a superscript under all three package combinations. The stale note indouble_bar_testssaying\Vertis a fence name only is now an assertion that it parses.Scope
The six names move from generated
math_symbolrows tomath_structurerows because they now carry a class, and\vert/\Vertare genuinely new, so--supportedgoes 542 → 544 math entries.docs/user/compiler.mdandsupported/supported-latex.jsonare regenerated bycrates/compiler/scripts/render_supported_latex.sh, whichtests/supported_latex.rsgates. No oracle data was regenerated.crates/render-pipelinebuilds againstvendor/compiler, so all of this is inert in the pipeline until that pin moves. No vendor copy is touched here; the re-pin is left to the integration lane, as#274did for#265.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xd5Hmwh5GHNTiAmHUJ1MZu