Float placement: a captured body that keeps its assignments, and floats that ride their own page - #196
Merged
Conversation
…loat on its own page Two defects in the gated single-column placer, both found by measuring it over the 157-paper arXiv corpus, where it was 1059 pages WORSE than the inline path. The whole of that came from one paper. A captured float body was executed with no group of its own, so an assignment inside it escaped into the document — where LaTeX sets the float inside \vbox\bgroup…\egroup (latex.ltx:12950) and the engine's own inline \@float opens a \begingroup that \end@float closes. The paper has a figure holding \put(-0.33\textwidth,0.5\textwidth){…}: \put is undefined here, so \textwidth reads as the start of an assignment and takes its missing number as zero, and \hsize stayed 0pt for the remaining 230 pages. They set one word per line, and the paper ran to 1439 pages against a reference of 333. Second, a float was only a candidate for a page when it was anchored BEFORE that page began, so every float came out at least a page late and, once the text ran out, on a float page of its own. LaTeX contributes a float at its anchor — \@Xfloat fires the output routine there — and \@addtocurcol (latex.ltx:15636) tests it against the room LEFT in the column: it rides the page being built whenever \@colroom exceeds the height already set plus \textfraction plus the float. Floats now join the page they are written on, in their own order per caption type (\@bitor\@currtype\@deferlist: figure 3 never precedes figure 2). Measured over 157 arXiv papers against tectonic, with GOTEX_FLOATS=1: page error 1691 -> 620 (inline, flag off: 632) exact 21 -> 21 (inline: 20) divergence 4.438 (inline: 4.408 — flat within this measure's noise) beamer is untouched (40 over 79 decks either way). The flag stays OFF by default; this makes the placer worth turning on, which is the next step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 3, 2026
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.
Measuring the gated single-column placer over the 157-paper arXiv corpus put it
1059 pages worse than the inline path it replaces. Two defects, both now
fixed and both read back against
latex.ltx.The body was executed without a group
All of those 1059 pages came from ONE paper. The captured float body ran with no
group of its own, so an assignment inside it escaped into the document — where
LaTeX sets a float inside
\vbox\bgroup…\egroup(latex.ltx:12950), and thisengine's own inline
\@floatopens a\begingroupthat\end@floatcloses.The paper has a figure holding
\put(-0.33\textwidth,0.5\textwidth){…}.\putis undefined here, so
\textwidthreads as the start of an assignment and takesits missing number as zero — exactly what TeX itself would do.
\hsizethenstayed 0pt for the remaining 230 pages, which set one word per line: 1439
pages against a reference of 333.
(Defining
\putand the rest ofltpicturis worth doing on its own; it is notwhat this PR is about. With the body properly grouped, an undefined command
inside a figure can no longer reach past it.)
A float could not ride the page it was written on
A float was only a candidate for a page when it was anchored BEFORE that page
began, so every float came out at least a page late and, once the text ran out,
on a float page of its own. LaTeX contributes a float at its anchor —
\@xfloatfires the output routine there with
\@floatpenalty— and\@addtocurcol(latex.ltx:15636) tests it against the room LEFT in the column: it rides the page
being built whenever
\@colroomexceeds the height already set plus\textfractionplus the float itself. That is why a figure declared halfway downa page comes out at the top of that page.
Floats now join the page they are written on, keeping their order per caption
type —
\@bitor\@currtype\@deferlist: figure 3 never precedes figure 2.Measured, 157 arXiv papers against tectonic,
GOTEX_FLOATS=1beamer is untouched: 40 pages of error over 79 decks either way. The divergence
difference is inside the noise band this measure showed when the same binary was
run over two different 50-paper samples (±0.07).
The flag stays OFF in this PR. What it changes is that turning it on is now an
improvement rather than a regression — that, with the figure geometry work of
issue #146, is the next step.
Two regression tests, each failing on
main: an assignment inside a capturedfigure leaves
\hsizealone, and a figure written inside a page comes out on it.