Skip to content

Fix the stacked-structure geometry the census could not yet see - #25

Merged
P4suta merged 4 commits into
mainfrom
fix/racket-stacked-geometry
Aug 24, 2026
Merged

Fix the stacked-structure geometry the census could not yet see#25
P4suta merged 4 commits into
mainfrom
fix/racket-stacked-geometry

Conversation

@P4suta

@P4suta P4suta commented Aug 24, 2026

Copy link
Copy Markdown
Owner

Two coordinates inside a warichu block were the last places the three reference engines
did not agree, and both are one reading of §3.4.2 that
docs/decisions/stacked-structure-geometry.md
turned on but never spelled out: a note's sublines run beside the line, and the block
they make is one position on it.

Fixes #23
Fixes #24

What was wrong

The block axis (#23). engines/racket/compose.rkt reported a line's block_extent
as the sum of the note's own subline heights wherever that was larger than the
paragraph's own block size. A note set at the paragraph's em rather than at the half em
§3.4.2's own sizing comes to therefore made the line twice as deep as the paragraph set
it, as though the two sublines were two lines of the paragraph.

(abcd) full-em, warichu [0,6), eight-em measure
before  (4000/2000)   after  (4000/1000)   ← Rust and OCaml, byte for byte

The boundary inside a subline (#24). A member reported its bare body as its advance,
so a member standing before a Table 1 amount inside its own row — cl-02 then cl-01,
half of the note's own em — reported 500 while the same engine placed the next member 750
units on. The answer's own advances did not reach the answer's own positions.

※〉〈あ※ half-em note, warichu [3,12), six-em measure
before  [... 1000:0+500 1750:0+500 ...]   after  [... 1000:0+750 1750:0+500 ...]

§3.8.3's half of the same reading. raw-gap-terms handed the reduction ladder every
interior boundary at face value, the block's own included, so a line that could not fit a
note squeezed the note's interior instead of overrunning — and gave back space that was
never on the line. The expansion ladder already stopped at a block, through
same-complex?; both now ask one predicate, same-block?.

What changed

  • engines/racket/compose.rkt: the warichu run contributes the paragraph's own block
    size to the line and nothing more; a member's advance is its body plus the amount after
    it inside its row, and the member that ends a row reports its body alone; neither
    ladder reads a boundary the line was not composed from. warichu-layout no longer
    answers a height nobody asks for.
  • engines/racket/tests/test-compose.rkt: three cases computed by hand from §3.4.2 and
    Table 1 — the full-size note's block extent and placements, the in-row amount and the
    advance that reaches it, and the overrun a three-em measure produces rather than a
    squeezed block. All three fail on the previous engine.
  • engines/ocaml/probe/census.ml: five further constructs variants and two further
    tabs ones reach both shapes at every one of the 529 class pairs —
    warichu-full-size and its vertical mirror, warichu-pair-inside,
    warichu-pair-inside-row, warichu-pair-inside-justified,
    inside-full-size-warichu and -found. Against the previous engine they report 1,643
    and 1,058 differing responses; against this one, none.
  • docs/decisions/stacked-structure-geometry.md publishes both readings and why, and the
    census counts every file quotes follow the new variants.

Measurements

Census Requests Rust vs OCaml Rust vs Racket
spacing 2,116 0 0
break 2,116 0 0
reduction 3,174 0 0
expansion 3,174 0 0
vertical 5,290 0 0
tate-chu-yoko 4,761 0 0
ruby 37,030 0 0
constructs 18,515 0 0
tabs 31,211 0 0
widow 13,225 0 0
total 120,612 0 0

just conform-racket, just conform-ocaml and the Rust sample engine each answer all
eighty-nine built-in cases at exit 0; just check, just design and just ci are green
locally.

One thing this PR deliberately does not ask

A sixth constructs variant — the same pair inside the note on a line that has to give
space back
— is absent, and its comment says why. Where a note's two sublines meet at a
Table 1 amount, the Rust and OCaml engines do not agree with each other: the Rust engine
divides the line's arrears over that in-block boundary and lays nothing down there, so it
gives back half of what it needs and reports layout.overfull; the OCaml engine does not
offer the boundary at all. This branch's Racket engine answers as OCaml does, which is
what the reading above says. Fifteen coordinates show it, all of the form
census/constructs/cl-01+cl-02/… — a repro is 漢〈〉あ漢 with the note at the half em, a
warichu over the three, and a three-em measure. Worth its own issue rather than a census
that reports a nonzero count.

While measuring it, one further Racket coordinate turned up that is not fixed here and
is nobody's regression: a furawake row is laid out from bare advances, so a member
before a Table 1 amount inside its row is placed and reported the way #24 described for a
warichu. Fixing it needs the boundary after a furawake block to be read against the
block's last character rather than its first, which is a wider change to how
compose.rkt makes one item of a furawake — also its own issue.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs

P4suta and others added 4 commits August 24, 2026 23:07
`engines/racket/compose.rkt` answered two coordinates inside an inline
cutting note differently from the other two engines, and both are one
reading of §3.4.2: a note's sublines run BESIDE the line, and the block
they make is one position on it.

On the block axis the engine reported a line's `block_extent` as the sum
of the note's own subline heights wherever that was larger than the
paragraph's own block size. A note set at the paragraph's em rather than
at the half em §3.4.2's own sizing comes to therefore made the line twice
as deep as the paragraph set it, as though the two sublines were two lines
of the paragraph. They are not: the line is as deep as the paragraph set
it, and how deep the block is is the block's own answer (#23).

On the inline axis a member of the note reported its bare body as its
advance. A member standing before a Table 1 amount inside its own subline
-- `cl-02` then `cl-01`, half of the note's own em -- reported 500 while
the same engine placed the next member 750 units on, so the answer's own
advances did not reach the answer's own positions (#24). A member's
advance is now its body plus the amount after it inside its row, and the
member that ends a row reports its body alone, because nothing of that row
stands after it.

§3.8.3's ladder is the other half of the second reading. A boundary the
line was not composed from is not one the line may take space back from,
so `raw-gap-terms` now stops at a block's interior exactly as the line's
own geometry does; the expansion ladder already did, through
`same-complex?`, and both now ask one predicate. A line beside a note it
cannot fit overruns rather than squeezing the note's own interior, which
is a width the block already reported to the line.

`docs/decisions/stacked-structure-geometry.md` publishes both readings.
No public API and no other engine changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs
…de it

The `constructs` and `tabs` censuses stood the pair under test at a
structure's two edges and set every note at the half em §3.4.2's own
sizing comes to, so two coordinates of a warichu block were outside what
any census asked: how deep a line carrying a note it does not fit is, and
what Table 1 does between two characters of one subline. Both were where
the Racket engine differed from the other two (#23, #24), and neither was
observable at scale.

Five further `constructs` variants and two further `tabs` ones ask them at
every one of the 529 class pairs. `warichu-full-size` and its vertical
mirror set the note at the paragraph's own em, which makes a block twice
as deep as the line that holds it and is the only shape where a block's
own depth and the line's come apart; `inside-full-size-warichu` and
`-found` put a tab sign in that same block, at a stop the line reaches and
at one it has gone past. `warichu-pair-inside` sets the pair inside the
note's own rows instead of beside them, `-row` moves it off the head of
its row in a five-character note the balance divides three and two, and
`-justified` asks §3.8.4's ladder whether it reaches into a block.

`constructs` is 18,515 requests and `tabs` is 31,211, which puts all ten
censuses at 120,612. §3.8.3's own half of the last variant is deliberately
absent: a line that has to give space back beside a note whose two
sublines meet at a Table 1 amount is a coordinate the Rust and OCaml
engines do not agree on themselves -- the Rust engine divides the line's
arrears over that in-block boundary and lays nothing down there, the OCaml
engine does not offer the boundary at all -- and a census asks the settled
question.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs
…erior

`docs/decisions/stacked-structure-geometry.md` published four readings
about a structure's own two edges and said nothing about the two
coordinates inside it, which is why an engine could miss both and still
be read as following the file. Both are now stated, and both are the same
sentence the file already turns on -- a structure occupies one position on
the line -- read on an axis and at a boundary it did not spell out.

The block a structure makes is not a depth the line reports: its sublines
run beside the line rather than being lines of their own, so a note set at
the paragraph's own em leaves the line one em deep and keeps its own two
ems to itself. And a character inside the block is spaced like any other:
Table 1 answers between two characters of one subline, a member's advance
is the step that reaches the member after it, the character that ends a
subline reports its body alone, and neither ladder reaches a boundary the
line was never composed from.

Both were reached by measurement before anything was written down here
(#23, #24), which is the footing the census findings in this directory
already stand on. The census counts every file quotes follow the seven new
variants: `constructs` is 18,515, `tabs` is 31,211, and the ten censuses
are 120,612 requests at zero differences across the three engines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs
`db4f6b4` bumps four GitHub Actions in the workflows and touches nothing
this branch changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs
@P4suta
P4suta merged commit 7b8b7bf into main Aug 24, 2026
25 checks passed
@P4suta
P4suta deleted the fix/racket-stacked-geometry branch August 24, 2026 14:13
P4suta added a commit that referenced this pull request Aug 24, 2026
… two ends (#28)

* fix(jlreq): stop §3.8.3's ladder at the block a warichu makes

`reduction_sites` offered the ladder every interior boundary of a stacked
structure. Two characters of one subline are one such boundary, and so is
the SEAM where a note's two sublines meet -- the last character of the
first and the first character of the second are adjacent in the text, so
Table 1 states an amount between them like anywhere else.

That amount stands nowhere. The character that ends a subline reports its
body alone and the character that begins the next one begins it at its own
row's origin, so the line was never composed from the seam and there is
nothing there to give back. Offering it to the ladder divides the line's
arrears over a site that cannot pay them: `※〈〉あ※` at a three-em measure
needs 250 units back, has one boundary on the line that can give them and
one seam that cannot, halves the debt between the two, recovers 125 and
reports `layout.overfull` about a line it could have made fit.

The expansion ladder already stopped at the block, through the same
construct-interior test `boundary_expansion_site` runs; the reduction
ladder now asks the same question of a warichu and a furawake. Neither
public API nor any other engine changed, and no other census coordinate
moves: this is the only shape where a boundary Table 1 names is not a
boundary the line was composed from.

Fixes #26

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs

* fix(racket): give a furawake its columns' own spacing and its own far edge

`compose.rkt` gathers a §3.7.2 furawake into ONE item of the line, where a
§3.4.2 warichu's members stay items of their own. Two things followed from
that and both were wrong.

A column was measured from bare cluster advances, so a member standing
before a Table 1 amount inside its own column -- `cl-02` then `cl-01`, half
of the member's own em -- reported 1000 while the same engine placed the
next member 1000 units on and charged the line 1500 for the step. That is
the defect #24 fixed for a warichu, at the other structure the same reading
is addressed to: a column is ordinary text of the structure, so a member's
advance is its body plus the amount after it inside its row, and the member
that ends a row reports its body alone.

And the one item carried the class of its FIRST cluster, so the boundary
AFTER the block was answered at the wrong end of it. A structure has two
edges and they are two different characters: the amount before the block is
Table 1's answer at the block's first character and the amount after it the
answer at its last. The `item` struct now carries a `trailing` edge -- the
occurrence a boundary after the item is read against, which is the item
itself for anything one character wide -- and every reading of the before
side of a boundary asks for it. A tate-chu-yoko run carries none: §3.2.5
makes it cl-30 at both of its edges however many characters it holds.

The two errors are equal and opposite wherever the two amounts are, which
is why `※〉〈日※` comes out 4500 wide either way and `※・・日※` does not.
Both halves had to move together. Neither public API nor any other engine
changed, and `across` in `furawake-item` -- computed and read by nobody
since the block's depth moved to `block-room` -- goes with them.

Fixes #27

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs

* docs(decisions): publish the seam and the two edges, and reach both at every class pair

`engines/ocaml/probe/census.ml` gains three `constructs` variants. The
first is the sixth shape PR #25 deliberately withheld:
`warichu-pair-inside-reduced` stands the pair under test inside a note on a
line that has to give space back, which is where the two answers to
"does §3.8.3's ladder reach the seam" come apart in the line's own width.
The other two are `furawake-pair-inside` and its vertical mirror, which
stand the pair inside one of a furawake's columns and put an ordinary
character in the other, so one request asks both what Table 1 does inside a
column and which of the block's characters the boundary after it is read
against.

The `constructs` census is 20,102 requests and all ten are at zero
differences across the three engines over 122,199. Against the previous
engines the same census reports 15 differing responses for the Rust one and
434 for the Racket one.

`docs/decisions/stacked-structure-geometry.md` states both readings, why
they follow from the sentences already published there rather than from
anything new, and what a revision would have to say to settle either one
the other way. The seam is the only boundary of a block that is neither
strictly inside a subline nor at the block's edge, which is how an engine
can reach it by asking Table 1 about two adjacent characters without asking
which object they are adjacent on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N8KNy2ejPqy81P2hVcRUjs

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant