Skip to content
Open
27 changes: 19 additions & 8 deletions apps/benchmarks/src/benchmark/package-size-budgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ export const packageSizeBudgets = {
},
// The renderer-neutral core subpath (D-249) must stay integration-free; the graph
// assertion in measure-package-sizes.mts already rejects any three/tsl/react pull.
// Grew with the technique-schema authority layer (D-251): declarations, validation,
// and the schema-typed store path. The review-closure pass added +3,525 raw /
// +1,686 minified / +351 Brotli of real validation, provenance, and derivation
// code (schema normalization and freezing, DSL session provenance,
// schemaPolicyBuffers/schemaFieldTable). Re-based when tsdown bundling lands per
// the technique contract plan.
'core-subpath-js': {
rawBytes: 215_000,
minifiedBytes: 148_000,
gzipBytes: 38_000,
brotliBytes: 32_500,
rawBytes: 226_000,
minifiedBytes: 155_000,
gzipBytes: 39_400,
brotliBytes: 33_900,
},
'tsl-subpath-js': {
rawBytes: 27_000,
Expand Down Expand Up @@ -47,11 +53,16 @@ export const packageSizeBudgets = {
gzipBytes: 429_000,
brotliBytes: 339_500,
},
// Raw rose for the policy-DSL authoring layer riding the Three bundle (D-250),
// then the review-closure pass added +3,535 raw / +1,711 minified / +439 Brotli
// of schema-derived executor lookups, program buffer derivation, and the
// glyph-origin schema map replacing literal id ranges. Real code, not comments;
// the compressed ceilings hold with tight headroom by design.
'three-runtime-js': {
rawBytes: 350_000,
minifiedBytes: 232_000,
gzipBytes: 60_000,
brotliBytes: 51_000,
rawBytes: 366_000,
minifiedBytes: 239_000,
gzipBytes: 61_800,
brotliBytes: 52_200,
},
'font-inter-bitmap-16-32': {
rawBytes: 3_200_000,
Expand Down
50 changes: 25 additions & 25 deletions apps/benchmarks/src/generated/package-sizes.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
"label": "Renderer-neutral core JS",
"status": "measured",
"format": "javascript",
"sha256": "bdbdb8a9112a9c600cd9020f816c9cbe30296d3298f0ae9c1f8e0f1de55c6f6e",
"rawBytes": 207759,
"minifiedBytes": 142016,
"gzipBytes": 36140,
"brotliBytes": 30853
"sha256": "54a83a8595d3756eee428549ceea7077f546372f655f5d091be5829737cefc4d",
"rawBytes": 225596,
"minifiedBytes": 154330,
"gzipBytes": 39021,
"brotliBytes": 33248
},
{
"id": "tsl-subpath-js",
Expand Down Expand Up @@ -54,11 +54,11 @@
"label": "Three.js adapter JS",
"status": "measured",
"format": "javascript",
"sha256": "0a231f0b3b658eecc1ed7eb1765e66b5f1b4ce53b01fc4405540351af1830df4",
"rawBytes": 347795,
"minifiedBytes": 227757,
"gzipBytes": 59005,
"brotliBytes": 49764
"sha256": "59fd78dbb413502ac3b9014bc51a0912eafb15cc588aa4732c459ca5a738cf19",
"rawBytes": 365690,
"minifiedBytes": 238911,
"gzipBytes": 61533,
"brotliBytes": 52080
},
{
"id": "font-inter-bitmap-16-32",
Expand Down Expand Up @@ -164,33 +164,33 @@
"label": "Bitmap runtime JS graph",
"status": "measured",
"format": "javascript",
"sha256": "8350a9c6113b00b0b3b6ad1acad12b88ffdeeff2f020075ee8a3f63f1a7f6421",
"rawBytes": 337047,
"minifiedBytes": 220443,
"gzipBytes": 57609,
"brotliBytes": 48186
"sha256": "7f2f6b109b49c430e8b70a35ae43e450d7c49477e8583cb0dff07a7ef604de21",
"rawBytes": 354942,
"minifiedBytes": 231588,
"gzipBytes": 60389,
"brotliBytes": 50563
},
{
"id": "mtsdf-runtime-js",
"label": "MSDF runtime JS graph",
"status": "measured",
"format": "javascript",
"sha256": "c05d0beaebc0d0aa81d79b9b7f7c5693b4d1d5c3e3757bfe8cf24ed76526b052",
"rawBytes": 337043,
"minifiedBytes": 220425,
"gzipBytes": 57590,
"brotliBytes": 48260
"sha256": "fcfc3e1cd4da4f7969cae2c4258c58cac52f3e9bff7c4a45748290a371e81180",
"rawBytes": 354938,
"minifiedBytes": 231569,
"gzipBytes": 60449,
"brotliBytes": 50503
},
{
"id": "slug-runtime-js",
"label": "Slug runtime JS graph",
"status": "measured",
"format": "javascript",
"sha256": "99d66c65a3c7c3a68ac5b8d0ec7dbfedc5d40bfc344ddc1b70f12d3495fbede2",
"rawBytes": 337045,
"minifiedBytes": 220509,
"gzipBytes": 57484,
"brotliBytes": 48145
"sha256": "9d938b57d027a7c910ef4355ebfb696462d562a4a545cb96f03fa4dfcd79484b",
"rawBytes": 354940,
"minifiedBytes": 231663,
"gzipBytes": 60303,
"brotliBytes": 50565
},
{
"id": "bitmap-baker-wasm",
Expand Down
40 changes: 40 additions & 0 deletions docs/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,46 @@

## 2026-08-11

- **Technique-contract review closure (D-250/D-251)** — Closed all four adversarial-review findings on the
schema-authority stack. Policy-DSL values now carry session provenance: a value loaded from one program's input
table throws when stored through another builder instead of silently reading a shifted input. Technique schemas
deep-freeze at definition, making the documented immutability true at runtime. The remaining smear sites now
derive from schemas: `schemaPolicyBuffers` replaces hand-rolled width lists in the Three programs,
`schemaFieldTable` orders binding-table readers by the schema's declared names so a misspelled or missing reader
is a compile error, the executor resolves draw buffers by schema name instead of literal id ranges, and the
plan-program registry references the transform system buffer instead of restating `15`. Glyph-origin
augmentation became schema-declared opt-in metadata (`glyphOrigin`) rather than assuming Bitmap's buffer layout
for every technique. The structural gate now also rejects literal-width buffer builders, literal id ranges, and
restated system ids. Every policy and binding byte golden stayed pinned — the derivations reproduce the
hand-rolled bytes exactly, decided by the existing decoded-equivalence proof. The adversarial re-review then
confirmed provenance and glyph-origin closed and surfaced follow-up defects, fixed in a second pass: session
provenance is now stamped at node construction and combined in O(1) — a shared expression DAG no longer costs an
exponential graph walk, and mixing sessions fails at the combinator itself; schema definition validates the
caller's input first and returns an owned, deeply frozen copy, so rejection leaves caller data untouched and a
hostile lanes accessor can never change a validated width; the size-budget notes now record the measured deltas
(+3.5 KB raw / +1.7 KB minified per surface of real validation and derivation code) instead of claiming
comment-dominated growth; and the package reference gained `/core` and `/tsl` rows with `/three/{bitmap,msdf,slug}`
described as the compatibility aliases they are. The re-review's remaining structural finding — TSL shader lane
meaning and the external example still restate schema knowledge — is migration layers 3–4 of the technique
contract plan, scheduled with the audit.

- **Technique schema authority (D-251)** — Buffer ids, lanes, and binding fields are declared once per technique
by colocated schemas; programs store through schema handles, the executor reads declared ids, and a repository
gate forbids literal buffer identity anywhere else. Policy bytes proven byte-identical across the change. The
full contract plan — shader-interface derivation, data origins including the reserved `pretext` fallback, and
the tsdown build — is recorded in docs/planning/raster-technique-contract.md.

- **Policy interpreter tail measurement** — Driving the kernel-lab explicit artifact at controlled record counts:
a 4-record vector iteration costs ~50–75 ns and each scalar tail record ~50 ns, so a span of 7 records
(1 vector + 3 scalar, 282 ns) costs more than the 8-record two-vector shape (181 ns) that a tail-overlap
rewrite would produce. Overlap would therefore save roughly 100 ns per tailed draw-span — real but bounded:
a frame needs thousands of tailed spans before it reaches microseconds. Not wired per the D-245 admission
bar; revisit if a workload profile ever shows many small draw-spans dominating the packing pass.

- **Policy authoring DSL (D-250)** — Policy programs are written against named semantic and binding handles with
automatic register allocation; the four Three programs ported with a decoded-bytes equivalence proof against the
hand-numbered fixtures and re-pinned goldens. Wire format and interpreter unchanged.

- **Core API surface (D-249)** — The renderer-neutral engine publishes as `@pmndrs/glyph/core` and the technique
shader library as `@pmndrs/glyph/tsl`. Three's first-party policy and the Slug shader tree leave core internals,
and a scoped import lint holds the first-party integrations to the same public surface a third party gets.
Expand Down
2 changes: 1 addition & 1 deletion docs/packages/benchmarks.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Provides the shared interactive and automated benchmark product sur
resource: ../../apps/benchmarks
workspace_package: '@pmndrs/glyph-benchmarks'
documentation_type: reference
source_digest: 'sha256:e9af38649ef27133babebf9a6503bc2297f5e8cd187c246da59e5b9611e49a45'
source_digest: 'sha256:66cf967802ebd6088bbb585004d67e03c894cb79c8776c0bd1f5a2a7c2c2bc1e'
tags: [package, benchmarks, react, vite, product-e2e]
sources:
- id: manifest
Expand Down
24 changes: 9 additions & 15 deletions docs/packages/glyph.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Implements portable font loading, retained Rust shaping and layout,
resource: ../../packages/glyph
workspace_package: '@pmndrs/glyph'
documentation_type: reference
source_digest: 'sha256:a05cfbb2b9c43ac971c71259b6d93e962d4550dd0c44a424b21c30f1b0980e59'
source_digest: 'sha256:165fd636d138709fe954feaea7b79188e6fb6271d45a9efdceb55f420e430b37'
tags: [package, public-api, rust, wasm, threejs, typography]
sources:
- id: manifest
Expand Down Expand Up @@ -100,27 +100,21 @@ TypeScript does not independently shape, lay out, or pack paragraphs.

## Public package surfaces

| Subpath | Purpose |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Subpath | Purpose |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `@pmndrs/glyph` | Font/raster contracts, loading, fallback stacks, formatting helpers, paragraph inputs, layout-query values, and portable bakers. |
| `@pmndrs/glyph/core` | Renderer-neutral engine host, frame wire, plan/layout-query views, technique schemas, policy-program DSL, and binding compiler. |
| `@pmndrs/glyph/tsl` | Canonical TSL shader realizations of the first-party technique interfaces; no scene integration. |
| `@pmndrs/glyph/three` | Three `FontLoader`, `Text`, `TextGroup`, material factories, and policy registration. |
| `@pmndrs/glyph/three/bitmap` | Bitmap technique, policy program, and canonical TSL shader. |
| `@pmndrs/glyph/three/msdf` | MSDF technique, policy program, and canonical TSL shader. |
| `@pmndrs/glyph/three/slug` | Slug technique, policy program, and canonical TSL shader. |
| `@pmndrs/glyph/three/bitmap` | Compatibility alias re-exporting the renderer-neutral Bitmap raster module. |
| `@pmndrs/glyph/three/msdf` | Compatibility alias re-exporting the renderer-neutral MSDF raster module. |
| `@pmndrs/glyph/three/slug` | Compatibility alias re-exporting the renderer-neutral Slug raster module. |
| `@pmndrs/glyph/react` | React `<Text>`, `<TextGroup>`, and `useFont`, reconciled through React Three Fiber. |
| `@pmndrs/glyph/bake` | Node programmatic font baking, glyph selection, and font inspection used by the `glyph` CLI. |
| `@pmndrs/glyph/bake` | Node programmatic font baking, glyph selection, and font inspection used by the `text` CLI. |
| `@pmndrs/glyph/runtime-bake` | Explicit browser Worker host for optional runtime baking. |
| `@pmndrs/glyph/raster/*` | Renderer-neutral Bitmap, MSDF, and Slug decoding and raster-technique contracts. |
| `@pmndrs/glyph/bakers/*` | Optional portable raster bakers and validators. |

## Canary releases

Pushes to `main` run the package's complete `check` lane and publish `@pmndrs/glyph` to npm under the `canary` dist-tag.
The workflow derives an immutable prerelease version from the manifest's base version, the first eight commit SHA
characters, and the UTC release date. npm trusted publishing authenticates the GitHub-hosted job with OIDC; the package
manifest pins the public npm registry and repository identity, and npm generates provenance for the public package.
The workflow carries no long-lived npm token.

The font-baker Rust source, direct-memory wrapper, schemas, tests, build pipeline, optimized Wasm, and generated ABI are
owned by this package. There is no separately published font-baker package. The root entry has no static edge to the
baker, its `std`-enabled dependencies, Ajv, glTF Validator, or the baker Wasm; only explicit bake/runtime-bake surfaces can
Expand Down
2 changes: 1 addition & 1 deletion docs/planning/benchmark-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Status key: ✅ specified or available · 🟡 partial or conditional · ⬜ not
| Harness gate | Status | Evidence required to advance |
| -------------------------------------------- | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Canonical architecture and scenario contract | ✅ | This plan owns one target registry, one scenario registry, and one runner contract for interactive and headless surfaces. |
| Portable baker target | ✅ | `packages/glyph/rust/font-baker` and the app run immutable Inter 4.1 bytes through the direct-memory Wasm API with deterministic GLB evidence. |
| Portable baker target | ✅ | `packages/glyph/rust/font-baker` and the app run immutable Inter 4.1 bytes through the direct-memory Wasm API with deterministic GLB evidence. |
| Lab shell under `apps/benchmarks` | ✅ | The responsive token/component shell defaults to the human-facing live benchmark with mode, technique, backend, and workload URL state; finite visual conformance is separate. Fixed histories report renderer-callback CPU time, FPS, and real WebGPU/WebGL2 GPU timestamps when supported, while capture/export snapshots the live contract on demand. Causal product checks own label fit, control density, horizontal overflow, and mobile/tablet/desktop flow at 390, 1,024, and 1,280 CSS pixels. |
| Headless product E2E | 🟡 | A browser CLI, Vitexec, and Playwright call the same strict registry execution module. The bounded CI-safe conformance suite includes synthetic, forced-WebGL2 TSL and bitmap rendering, public React `Text` reconciliation, direct-baker, loader/Worker, HarfRust, paragraph, bidi/policy/uikit, and item-5.4 CJK lanes. Hardware-WebGPU and pending-Suspense probes remain maintainer-local, and Milestone 6 awaits its closure review. |
| Package-size lane | ✅ | Independent library-mode entries produce nonzero raw/minified/gzip/Brotli initial-core, Unicode 17 analysis, lazy-validator, runtime-host, runtime-Worker, baker, and shaper JavaScript sizes plus raw/gzip/Brotli Wasm. Rollup static closures exclude dynamic chunks; the browser-core lane externalizes declared `three`, React, and R3F peers, while Worker and shaper JavaScript exclude separately measured Wasm assets. The record names its measurement host: same-host output stays exact, while every foreign-host entry must satisfy the shared complete reviewed budgets. Unicode analysis is 139,936 bytes minified and the Darwin arm64 shaper record is 32,778 bytes minified JavaScript plus 680,312 bytes optimized Wasm. |
Expand Down
Loading
Loading