Skip to content

text-2: the D-M deciding spike — shaped text joins low - #73

Merged
softmarshmallow merged 5 commits into
mainfrom
text-2-join-spike
Aug 5, 2026
Merged

text-2: the D-M deciding spike — shaped text joins low#73
softmarshmallow merged 5 commits into
mainfrom
text-2-join-spike

Conversation

@softmarshmallow

@softmarshmallow softmarshmallow commented Aug 5, 2026

Copy link
Copy Markdown
Member

text-2 — the D-M deciding spike: shaped text joins low

The join-point finding gated D-M's last open stage on one experiment: when the Web family gains a real shaped-text producer, push a text run from both it and n0 at a candidate neutral shaped-text + font-key contract, and observe whether the neutral boundary holds (text joins high — rframe gains a text fact) or breaks (text joins low — each engine keeps its own text artifact). With textlayout live (#70) and rendering (#71, #72), the two-producers-first gate was satisfied for the first time. This PR runs the spike, takes the decision (delegated by the owner for this stage), and wires it fully.

The spike

crates/n0/src/text_join_spike.rs — in-tree and unit-test-only, the same witness shape as the vector stage's drawlist_vector_join_spike.rs. textlayout enters n0 as a dev-dependency for this evidence alone; a section-membership lock in the spike holds it out of the shipping graph. The candidate contract is spike-local by design: content-digest font key (digest + face index + variations), font size, baseline-relative glyph placements, advance, metrics — no live object, no registry address, no raster-facing flags.

What it measured — on two declared platforms

The per-platform measurements follow the corpus's ramp-quantization protocol (an undeclared platform fails loudly with its full measured matrix; Linux mapped itself through the CI rounds on this branch):

arm Darwin-arm64 (CoreText) Linux-x86_64 (FreeType)
glyph identity joins bit-exactly (ids 35/36/3/37) joins bit-exactly
n0's pen placement exact (0/20/40/60) each em advance 2⁻¹⁶ short
n0's metrics ascent/descent/baseline 2⁻¹⁴ off exact
artifact-stream realization bilevel exact ink on the lattice bilevel exact ink on the lattice
backend extraction vs stream byte-exact everywhere byte-exact everywhere
n0's quantized run realized exactly sub-coverage (0 bytes) one alpha code value on the run's final edge (60 bytes)
n0's glyph replay vs exact outlines 432-byte non-bilevel fringe on the lattice (AA mask policy; stripped control exact) lattice-exact — replay's subpixel snapping rewrites the quantized placement back onto the lattice

The Web producer (textlayout) states exact font-unit arithmetic on every platform. n0's producer facts pass through its scaler backend, and which facts arrive exact is itself platform-dependent — one producer's facts are not even self-consistent across platforms. And the two builds fail in opposite directions: CoreText replay fringes where exact realization is clean; FreeType realization shows the producer's quantum where its replay silently repairs it. A producer's facts and its replay policy cancel only inside the matched engine-private pair — split across a neutral contract, each is a silent wrong pixel somewhere.

Plus the structural legs: realizing the candidate requires a digest→bytes environment parameter and gains an undeclared-key refusal — the exact boundary rframe's standing identity refuses and n0::glyphless is named for (n0's artifact carries process-local identity only; the digest never flows through its oracle). And beyond the overlap the second producer still does not exist: line structure refuses by type; styled runs and variable instances are not expressible in v0's input surface.

The decision

Shaped text joins low (D-M text stage, 2026-08-05 — D-M is now complete). Each engine's private compiler and executor retain their own text artifact, font registry, glyph replay policy, and text item. Sharing stops at backend glyph/raster utilities, licensed by the glyph-identity agreement. Content-digest font identity stays a host-seam convention — the CLI --font surface, textlayout::FontKey, and the bake manifests already share it by convergence — never a contract fact. The Web route keeps lowering glyphs to the artifact's own outline stream before rframe: the one realization measured platform-invariant and byte-exact where the oracle gates.

Re-opening is a new registered decision and requires both: a declared resource environment entering the shared contract for some other fact kind on its own evidence, and a measured need for cross-contract glyph replay. Producer maturity alone does not re-open it — the deciding legs are independent of how much text the Web producer can state.

Wired

  • docs/wg/consolidation/n0-join-point.md — status complete; the deciding-fact row resolved; new "The text-stage evidence" section
  • docs/wg/consolidation/charter.md — D-M registry row: both stages taken; phase-2 mechanism and exit updated to the per-engine route
  • index.md, web-first.md (dated subsequent-status), web-renderer-adoption.md, text-oracle.md, paint-vocabulary-gap.md (text leaves go engine-private), topology.md (dated amendment), CLAUDE.md — every "stage remains open" statement resolved
  • crates/rframe/tests/architecture.rs — two decided-refusal locks with separate provenance: no shaped-text fact (D-M text) and no resource reference (standing identity; phase-3 images revisit on their own evidence)
  • crates/rframe/src/frame.rs, crates/textlayout/src/lib.rs, crates/websem/src/svg.rs — module docs state the decided boundary

An adversarial four-lens review (laws, evidence validity, refute-the-verdict, record consistency) ran against the tree before the first commit; the refute lens graded the verdict sound, and its findings — stale open-stage statements, lock provenance, evidence-claim precision — are incorporated.

…fusals

Push one real text run from each producer — textlayout at oracle v0, n0's
Skia Paragraph oracle — at a spike-local candidate neutral shaped-text +
font-key contract, and record where the boundary holds and where it fails.
The spike is the two-producer experiment the join-point finding gated the
text stage on; like the vector spike, it lives in n0's unit-test build and
survives as the witness behind the taken decision.

Measured (Darwin-arm64; a new platform declares through one loud CI
round-trip, the corpus's ramp-quantization protocol):

- Shaping facts join bit-exactly: glyph ids, pen positions, advance. The
  content key matches by construction only — n0's artifact carries
  process-local identity and cannot state it.
- Metric facts arrive scaler-quantized (2^-14 per metric from the CoreText
  build) against the Web side's exact font-unit arithmetic.
- Two outline extractions (Skia get_path; ttf-parser stream) agree
  byte-exactly at every probed anchor and are bilevel on the lattice; n0's
  glyph replay through its oracle's live Font paints a 432-byte
  non-bilevel fringe on the lattice itself, and a policy-stripped control
  (alias, unhinted, no subpixel) matches the outlines byte-exactly —
  the fringe is the anti-alias mask policy.
- Realizing the candidate requires a digest→bytes environment parameter
  and gains an undeclared-key refusal — the resource boundary rframe's
  standing identity refuses and n0::glyphless is named for.
- Beyond the overlap the second producer still does not exist: line
  structure refuses by type; styled runs and variable instances are not
  expressible in v0's input surface.

The decided refusals gain their locks: rframe's architecture test now
holds "no shaped-text fact" under D-M-text provenance and "no resource
reference" under the contract's standing identity (phase 3 revisits that
one on its own image evidence); textlayout enters n0 as a dev-dependency
for this evidence alone, held dev-only by a section-membership lock in the
spike; the websem text compiler's outline lowering is re-documented as the
low join's mandate rather than a posture keeping the decision open.
…below the join

The delegated decision, recorded: shaped text joins low, on the
two-producer spike (crates/n0/src/text_join_spike.rs). D-M is complete —
vector high (2026-07-23), text low (2026-08-05).

The join-point finding gains "The text-stage evidence": the measured arms,
the decision, and the re-opening bar — a declared resource environment
entering the contract for another fact kind on its own evidence AND a
measured need for cross-contract glyph replay; producer maturity alone
does not re-open, because the deciding legs (metric fact identity,
pixel-visible replay policy, the one-meaning tripwire) are independent of
how much text the Web producer can state.

Propagated to every record that said the stage was open: the charter
(D-M registry row; phase-2 mechanism and exit — the engine-of-record
route gates byte-exact under the text-oracle method, n0's private text
stays gated by its own duty-cycle oracles), the consolidation index,
web-first (dated subsequent-status note), the adoption patrol, the
text-oracle brief, the paint-vocabulary gap report (text leaves go
engine-private), topology (dated amendment reconciling the shaped-text
bridge as a contract each engine's own producer implements), and the
CLAUDE.md textlayout row.
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
nothing Ready Ready Preview Aug 5, 2026 7:03am

Request Review

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR closes the D-M shaped-text decision at a low join. Web and n0 retain engine-local text artifacts, font identity, and replay policy. Tests verify the boundary, while documentation records the decision and supporting evidence.

Changes

Shaped-text join

Layer / File(s) Summary
Two-producer text spike
crates/n0/src/text_join_spike.rs, crates/n0/src/drawlist.rs, crates/n0/Cargo.toml
The test-only spike compares Web and n0 shaping, outlines, metrics, raster policies, font identity, resource handling, input support, and dependency placement.
Shared contract boundary
crates/rframe/src/frame.rs, crates/rframe/tests/architecture.rs
The rframe contract documents outline-only Web text. Architecture tests reject shaped-text and resource identifiers.
Producer ownership contracts
crates/textlayout/src/lib.rs, crates/websem/src/svg.rs
The documentation records engine-local text artifacts and Web outline lowering. n0 uses textlayout only for development tests.
Consolidation decision records
docs/wg/consolidation/*, AGENTS.md
The D-M text stage is recorded as taken at a low join. The documents define private text artifacts, registries, replay policies, paint handling, and oracle requirements.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

  • gridaco/nothing#70 — The PR builds on the textlayout APIs and adds cross-engine text-joining evidence.
  • gridaco/nothing#71 — The PR documents and tests the boundary created by Web SVG text shaping and outline lowering.
  • gridaco/nothing#68 — The PR updates the text-oracle records after the shaped-text join decision.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely states that the D-M deciding spike resolved the shaped-text join at the low boundary.
Description check ✅ Passed The description directly explains the shaped-text spike, its measurements, the low-join decision, and the related code and documentation changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch text-2-join-spike

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (5)
crates/n0/src/text_join_spike.rs (4)

590-596: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the fringe assertion prove the claim it states.

The comment says every differing byte is a partial-coverage value the outline render does not contain. The two assertions compare only counts. Two disjoint byte sets of size 432 would also satisfy them. Check the containment directly so the evidence matches the prose.

♻️ Set-based containment check

Add a helper next to differing_bytes:

/// Indices where `a` and `b` differ.
fn differing_indices(a: &[u8], b: &[u8]) -> Vec<usize> {
    assert_eq!(a.len(), b.len());
    a.iter()
        .zip(b)
        .enumerate()
        .filter(|(_, (left, right))| left != right)
        .map(|(index, _)| index)
        .collect()
}

Then assert containment at the lattice anchor:

     let replayed = realize_glyph_replay(&run, &font, LATTICE_ANCHOR);
     let resolved = realize_candidate_outlines(&run, &environment, LATTICE_ANCHOR).unwrap();
-    assert_eq!(differing_bytes(&replayed, &resolved), on_lattice);
-    // The divergence is a smoothing fringe, not displaced ink: every
-    // differing byte is a partial-coverage value the outline render does
-    // not contain.
-    assert_eq!(non_bilevel_bytes(&replayed), on_lattice);
+    let differing = differing_indices(&replayed, &resolved);
+    assert_eq!(differing.len(), on_lattice);
+    // The divergence is a smoothing fringe, not displaced ink: every
+    // differing byte is a partial-coverage value the outline render does
+    // not contain.
+    assert_eq!(non_bilevel_bytes(&replayed), on_lattice);
+    for index in &differing {
+        let byte = replayed[*index];
+        assert!(
+            byte != 0 && byte != 255,
+            "byte {index} differs at bilevel value {byte}: displaced ink, not a fringe"
+        );
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/n0/src/text_join_spike.rs` around lines 590 - 596, Update the fringe
validation near differing_bytes so it proves index containment rather than only
matching counts. Add the differing_indices helper beside differing_bytes, then
assert that every differing replayed index at LATTICE_ANCHOR has a non-bilevel
replayed value and that the corresponding resolved outline value is
bilevel/absent, preserving the existing count checks only if still useful.

708-727: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Close the dotted-key bypass in the dependency lock.

The match tests textlayout = and package = "textlayout". Cargo also accepts dotted keys, for example textlayout.workspace = true or textlayout.path = "../textlayout". Neither form matches. If a future change declares textlayout.workspace = true under [dependencies] and keeps the current [dev-dependencies] entry, declared_in_dev stays true and the lock passes while n0 ships the Web producer. Match the dotted form too.

Also capture the section name without a trailing comment. A line such as [dev-dependencies] # spike only currently compares unequal and fails with a confusing message.

♻️ Widen the match and normalize the section name
     for line in manifest.lines() {
         let trimmed = line.trim_start();
         if trimmed.starts_with('[') {
-            section = trimmed.to_string();
+            section = trimmed
+                .split('#')
+                .next()
+                .unwrap_or(trimmed)
+                .trim_end()
+                .to_string();
             continue;
         }
-        let names_the_crate =
-            trimmed.starts_with("textlayout =") || trimmed.contains("package = \"textlayout\"");
+        let names_the_crate = trimmed.starts_with("textlayout =")
+            || trimmed.starts_with("textlayout.")
+            || trimmed.contains("package = \"textlayout\"");
         if names_the_crate {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/n0/src/text_join_spike.rs` around lines 708 - 727, Update the manifest
scan in the dependency-checking loop to recognize both direct and dotted
`textlayout` keys, including forms such as `textlayout.workspace` and
`textlayout.path`, while preserving the existing dev-dependency validation.
Normalize section headers by removing any trailing comment and surrounding
whitespace before storing them in `section`, so headers like `[dev-dependencies]
# spike only` compare correctly.

267-309: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the hardcoded face_index the same way the digest is documented.

The doc comment explains why the digest is joined back from the host declaration. It does not explain face_index: 0 at line 307. candidate_from_web reads layout.face().face_index from its artifact, so the two projections differ in origin. If n0's artifact also cannot state a face index, record that as a third projection fact. If it can state one, read it instead of pinning the literal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/n0/src/text_join_spike.rs` around lines 267 - 309, Document the origin
of CandidateFontKey.face_index in candidate_from_n0 alongside the existing
digest projection fact. Determine whether the n0 layout exposes an artifact face
index: if so, populate face_index from that value; otherwise retain 0 and
explicitly document why the n0 projection cannot provide it, contrasting it with
candidate_from_web.

169-179: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider comparing float facts by bits to match the stated claim.

The module doc and the arm name state "bit-exactly". assert_eq! on f32 uses value equality, so -0.0 and 0.0 compare equal. The measured values here are 0.0, 20.0, 40.0, 60.0, so the current result is correct. If you want the assertion to enforce the exact claim, project the floats through to_bits().

♻️ Bit-exact projection
-    fn shaping_facts(&self) -> (&CandidateFontKey, f32, Vec<(u16, f32)>, f32) {
+    fn shaping_facts(&self) -> (&CandidateFontKey, u32, Vec<(u16, u32)>, u32) {
         (
             &self.key,
-            self.font_size,
+            self.font_size.to_bits(),
             self.glyphs
                 .iter()
-                .map(|glyph| (glyph.id, glyph.x))
+                .map(|glyph| (glyph.id, glyph.x.to_bits()))
                 .collect(),
-            self.advance,
+            self.advance.to_bits(),
         )
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/n0/src/text_join_spike.rs` around lines 169 - 179, Update
shaping_facts to project every f32 value through to_bits(), including font_size,
glyph x values, and advance, so assert_eq! enforces bit-exact comparisons and
distinguishes values such as -0.0 and 0.0.
docs/wg/consolidation/topology.md (1)

26-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Broaden the redirect to reach the Text section.

The redirect at lines 30-35 is scoped to places where the doc names the shaped-text artifact "as a bridge contract". The Text section at lines 149-163 does not use that phrase. It states "The chassis owns the seam" at line 151 and "All of it migrates through the artifact, not around it" at line 157. Both read as a single shared artifact, which the amendment reverses. A reader who starts at the Text section gets the pre-amendment meaning.

Either widen the redirect to cover every mention of the shaped-layout artifact in this doc, or add a one-line pointer to this amendment inside the Text section.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wg/consolidation/topology.md` around lines 26 - 36, Broaden the
amendment in the document so it also redirects the Text section’s statements
that imply a single shared shaped-text artifact, including “The chassis owns the
seam” and “All of it migrates through the artifact.” Either make the existing
redirect cover every such mention or add a concise pointer to this amendment
within the Text section, preserving the per-engine text-resolution contract
meaning.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/textlayout/src/lib.rs`:
- Around line 21-27: Update the module documentation link near the D-M
shaped-text stage reference to use an explicit same-repository relative Markdown
destination for docs/wg/consolidation/n0-join-point.md, avoiding intra-doc link
resolution and preserving the visible document path.

In `@crates/websem/src/svg.rs`:
- Around line 2358-2363: Rewrite the incomplete sentence in the `<text>`
contract documentation near the D-M shaped-text join discussion so it is
grammatically complete, while preserving the existing meaning about the
undecided posture, the mandated low join, and the contract carrying no text fact
or resource reference.

In `@docs/wg/consolidation/n0-join-point.md`:
- Line 259: Update the condition wording in the decision text to replace
“requires both of:” with “requires both:” while preserving the two following
conditions.

In `@docs/wg/consolidation/paint-vocabulary-gap.md`:
- Line 178: Update the “Text paint partition” entry to make clear that its Open
status covers only per-engine text-paint gates and is outside the taken vector
scope, consistent with the completed D-M text stage described in the referenced
consolidation documents.

In `@docs/wg/consolidation/web-first.md`:
- Around line 145-150: Update the shared-boundary list near the bullets for
shaped-text artifacts and resource references so it no longer contradicts the
subsequent status block and the established no-text-fact position. Remove or
clearly annotate those stale bullets as superseded, while preserving the
document’s conclusion that the shared contract gains neither a text fact nor a
resource reference.

---

Nitpick comments:
In `@crates/n0/src/text_join_spike.rs`:
- Around line 590-596: Update the fringe validation near differing_bytes so it
proves index containment rather than only matching counts. Add the
differing_indices helper beside differing_bytes, then assert that every
differing replayed index at LATTICE_ANCHOR has a non-bilevel replayed value and
that the corresponding resolved outline value is bilevel/absent, preserving the
existing count checks only if still useful.
- Around line 708-727: Update the manifest scan in the dependency-checking loop
to recognize both direct and dotted `textlayout` keys, including forms such as
`textlayout.workspace` and `textlayout.path`, while preserving the existing
dev-dependency validation. Normalize section headers by removing any trailing
comment and surrounding whitespace before storing them in `section`, so headers
like `[dev-dependencies] # spike only` compare correctly.
- Around line 267-309: Document the origin of CandidateFontKey.face_index in
candidate_from_n0 alongside the existing digest projection fact. Determine
whether the n0 layout exposes an artifact face index: if so, populate face_index
from that value; otherwise retain 0 and explicitly document why the n0
projection cannot provide it, contrasting it with candidate_from_web.
- Around line 169-179: Update shaping_facts to project every f32 value through
to_bits(), including font_size, glyph x values, and advance, so assert_eq!
enforces bit-exact comparisons and distinguishes values such as -0.0 and 0.0.

In `@docs/wg/consolidation/topology.md`:
- Around line 26-36: Broaden the amendment in the document so it also redirects
the Text section’s statements that imply a single shared shaped-text artifact,
including “The chassis owns the seam” and “All of it migrates through the
artifact.” Either make the existing redirect cover every such mention or add a
concise pointer to this amendment within the Text section, preserving the
per-engine text-resolution contract meaning.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4a18ab35-450c-4760-8158-7b5be4d15897

📥 Commits

Reviewing files that changed from the base of the PR and between 7634fa3 and a6e5667.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (16)
  • AGENTS.md
  • crates/n0/Cargo.toml
  • crates/n0/src/drawlist.rs
  • crates/n0/src/text_join_spike.rs
  • crates/rframe/src/frame.rs
  • crates/rframe/tests/architecture.rs
  • crates/textlayout/src/lib.rs
  • crates/websem/src/svg.rs
  • docs/wg/consolidation/charter.md
  • docs/wg/consolidation/index.md
  • docs/wg/consolidation/n0-join-point.md
  • docs/wg/consolidation/paint-vocabulary-gap.md
  • docs/wg/consolidation/text-oracle.md
  • docs/wg/consolidation/topology.md
  • docs/wg/consolidation/web-first.md
  • docs/wg/consolidation/web-renderer-adoption.md

Comment thread crates/textlayout/src/lib.rs
Comment thread crates/websem/src/svg.rs

The decision fixes where the *contract* boundary sits; it does not forbid the
engines from later sharing a shaping oracle as a utility below it. Re-opening
is a new registered decision and requires both of: a declared resource

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the condition wording.

requires both of: is not valid technical prose. Use requires both: before the two conditions.

🧰 Tools
🪛 LanguageTool

[style] ~259-~259: Consider replacing this word to strengthen your wording.
Context: ...Re-opening is a new registered decision and requires both of: a declared resource e...

(AND_THAT)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wg/consolidation/n0-join-point.md` at line 259, Update the condition
wording in the decision text to replace “requires both of:” with “requires
both:” while preserving the two following conditions.

Source: Linters/SAST tools

| Other blends, tile/alignment, gradients, and image paints | `cg` is the vector seat, but these leaves are not adopted by the solid-fill proving scope. | Preserve the full field matrices and settle transform/math/serialization policy; amendment-dependent gradient behavior and resource-bearing image facts retain their own gates. |
| Stroke application | `cg` is the selected seat, but its current ungrouped surface is not conforming and remains unmapped into production. | Add grouped, repeatable applications without flattening, then run the stroke laws directly. |
| Text paint partition | Open and outside the taken vector scope. | Resolve the text-stage decision, tri-state run-fill amendment, decoration color, and full run stroke applications. |
| Text paint partition | Open and outside the taken vector scope. The text-stage decision has since resolved **low** (2026-08-05, [the text-stage evidence](./n0-join-point.md#the-text-stage-evidence)): text paints never cross the shared contract, so this partition is each engine's private question, not a shared-seat adoption. | Tri-state run-fill amendment, decoration color, and full run stroke applications — now gated per engine, below the join. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clarify the status of the text paint partition.

If Open refers only to the remaining per-engine text-paint gates, make that scope explicit. docs/wg/consolidation/index.md Lines 69-74 and docs/wg/consolidation/n0-join-point.md Lines 245-281 mark the D-M text stage as complete. Use wording such as Per-engine and outside the taken vector scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wg/consolidation/paint-vocabulary-gap.md` at line 178, Update the “Text
paint partition” entry to make clear that its Open status covers only per-engine
text-paint gates and is outside the taken vector scope, consistent with the
completed D-M text stage described in the referenced consolidation documents.

Comment on lines +145 to +150
> **Subsequent status (2026-08-05):** the D-M shaped-text stage is taken
> **low** on the two-producer spike
> ([the text-stage evidence](./n0-join-point.md#the-text-stage-evidence)):
> shaped text stays below the join in each engine's private tier, and the
> shared contract gains no text fact and no resource reference. The generic
> frontend trait remains deferred.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Reconcile the new status block with the shared-boundary list.

Line 149 states the shared contract gains no text fact and no resource reference. The shared-boundary list at lines 90-91 still declares that the common product carries "shaped-text artifacts" and "resource references and exact environment revisions". The two statements are opposed, and nothing marks lines 90-91 as superseded.

AGENTS.md line 112 and the new rframe architecture tests both hold the no-text-fact position, so lines 90-91 are the stale side. Strike or annotate those two bullets so the document states one answer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/wg/consolidation/web-first.md` around lines 145 - 150, Update the
shared-boundary list near the bullets for shaped-text artifacts and resource
references so it no longer contradicts the subsequent status block and the
established no-text-fact position. Remove or clearly annotate those stale
bullets as superseded, while preserving the document’s conclusion that the
shared contract gains neither a text fact nor a resource reference.

The first Linux CI round reported, per the declaration protocol, and its
measurements sharpen the record in two ways:

- The FreeType build's metrics are exact (quantum 0) while its pen
  placement is not: each Ahem em advance arrives one 2^-16 step short —
  the mirror image of CoreText (exact pen x, 2^-14 metrics). Which of
  n0's resolution facts arrive exact is itself platform-dependent, so the
  shaping arm restructures: the platform-invariant join is glyph
  *identity*; placement is declared per platform like the metrics.
- Skia's get_path is policy-tinted by default on FreeType (hinting moved
  the extracted outline by 60 lattice bytes). The candidate consumer now
  strips hinting explicitly — a consumer realizing meaning must — and the
  Linux replay divergence deliberately stays undeclared so the next round
  re-measures it against the corrected baseline.

The paper's evidence bullets and the charter's D-M row are restated to
match: glyph identities join bit-exactly; placement and metric facts are
the scaler's, per platform.
The second Linux round refuted the hinting attribution: the 60-byte
lattice divergence between backend outline extraction and the artifact's
stream persists unhinted. The finding is sharper than the fix it
disproves — on the FreeType build the scaler's quantum reaches pixels
through *extraction itself* (one box edge lands off the lattice and
fringes), so the artifact's own outline stream is the one
platform-invariant realization, and everything through a backend font
object is that backend's policy.

The two pixel arms restructure accordingly, applying the corpus's
sweep-before-failing lesson: one platform-invariant arm (the artifact
stream is bilevel exact ink on the lattice, everywhere) and one declared
realization matrix (extraction vs stream, quantum invariance, replay vs
consumer, fringe shape, the policy-stripped control) measured in a single
sweep — an undeclared platform now reports its complete matrix in one CI
round. Darwin-arm64 is declared; Linux declares from the next round's
report. The paper's outline bullet and the consumer's comment are
restated to the measured mechanism.
…ding

The swept round reported Linux's complete matrix, and it corrects the
previous commit's mechanism story: extraction is byte-exact against the
artifact stream on both declared builds — the 60 lattice bytes were never
extraction. They are the producer's own scaler-quantized placement
reaching pixels when realized exactly (one alpha code value along the
run's final edge), while FreeType's glyph replay is lattice-exact and
bilevel precisely because its subpixel phase snapping rewrites that
quantized placement back onto the lattice.

The two declared builds therefore fail in opposite directions: CoreText
replay fringes where exact realization is clean; FreeType realization
shows the producer's quantum where replay silently repairs it. A
producer's facts and its replay policy cancel only inside the matched
engine-private pair — split across a neutral contract, each is a silent
wrong pixel somewhere. Declared in the matrix; the paper's placement and
realization bullets restated to the measured mechanism.
@softmarshmallow
softmarshmallow merged commit ff468a5 into main Aug 5, 2026
15 checks passed
@softmarshmallow
softmarshmallow deleted the text-2-join-spike branch August 5, 2026 07:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant