diff --git a/apps/benchmarks/scripts/generate-japanese-showcase-subset.mts b/apps/benchmarks/scripts/generate-japanese-showcase-subset.mts index 865e8268..9ff163b7 100644 --- a/apps/benchmarks/scripts/generate-japanese-showcase-subset.mts +++ b/apps/benchmarks/scripts/generate-japanese-showcase-subset.mts @@ -4,7 +4,7 @@ import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promi import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; -import { ADVANCED_SHAPING_CASES } from '../src/benchmark/advanced-shaping.ts'; +import { ADVANCED_SHAPING_CASES } from '../src/workloads/advanced-shaping.ts'; const harfBuzzVersion = '13.0.0'; const sourceDirectory = resolve('fixtures/fonts/noto-sans-cjk-2.004'); diff --git a/apps/benchmarks/src/app.tsx b/apps/benchmarks/src/app.tsx index 04eb43ad..d059b7c5 100644 --- a/apps/benchmarks/src/app.tsx +++ b/apps/benchmarks/src/app.tsx @@ -14,7 +14,7 @@ import { type RefObject, } from 'react'; -import { BENCHMARK_IPSUM_INTER_GLYPH_COUNT } from './benchmark/benchmark-ipsum'; +import { BENCHMARK_IPSUM_INTER_GLYPH_COUNT } from './workloads/benchmark-ipsum'; import { advanceAdvancedShapingByTime, advancedShapingCase, @@ -23,7 +23,7 @@ import { updateAdvancedShaping, type AdvancedShapingCommand, type AdvancedShapingFrame, -} from './benchmark/advanced-shaping'; +} from './workloads/advanced-shaping'; import type { BenchmarkSummary, RunnerEvent } from './benchmark/contracts'; import { environmentResource } from './benchmark/environment'; import { runRegisteredBenchmark } from './benchmark/execution'; diff --git a/apps/benchmarks/src/benchmark/font-fixtures.ts b/apps/benchmarks/src/benchmark/font-fixtures.ts index beacba62..88f440d4 100644 --- a/apps/benchmarks/src/benchmark/font-fixtures.ts +++ b/apps/benchmarks/src/benchmark/font-fixtures.ts @@ -1,5 +1,5 @@ -import type { AdvancedShapingFontFixture } from './advanced-shaping'; -import { BENCHMARK_IPSUM_CONFORMANCE_TEXT, BENCHMARK_IPSUM_TEXT } from './benchmark-ipsum'; +import type { AdvancedShapingFontFixture } from '../workloads/advanced-shaping'; +import { BENCHMARK_IPSUM_CONFORMANCE_TEXT, BENCHMARK_IPSUM_TEXT } from '../workloads/benchmark-ipsum'; export type BenchmarkFontFixture = | AdvancedShapingFontFixture diff --git a/apps/benchmarks/src/benchmark/presentation-sequence.test.ts b/apps/benchmarks/src/benchmark/presentation-sequence.test.ts index 1d411302..4d8ee3b1 100644 --- a/apps/benchmarks/src/benchmark/presentation-sequence.test.ts +++ b/apps/benchmarks/src/benchmark/presentation-sequence.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { ADVANCED_SHAPING_PRESENTATION_CYCLE_DURATION_MS } from './advanced-shaping'; +import { ADVANCED_SHAPING_PRESENTATION_CYCLE_DURATION_MS } from '../workloads/advanced-shaping'; import { adjacentPresentationWorkload, PRESENTATION_SCENES, diff --git a/apps/benchmarks/src/benchmark/presentation-sequence.ts b/apps/benchmarks/src/benchmark/presentation-sequence.ts index 5ceafdb7..3ab1ee9a 100644 --- a/apps/benchmarks/src/benchmark/presentation-sequence.ts +++ b/apps/benchmarks/src/benchmark/presentation-sequence.ts @@ -1,4 +1,4 @@ -import { ADVANCED_SHAPING_PRESENTATION_CYCLE_DURATION_MS } from './advanced-shaping'; +import { ADVANCED_SHAPING_PRESENTATION_CYCLE_DURATION_MS } from '../workloads/advanced-shaping'; export const TEXT_LADDER_PRESENTATION_DURATION_MS = 7_200; // Three complete 3.5-second base cycles at the default 1.125× animation rate, rounded up to the next millisecond. diff --git a/apps/benchmarks/src/benchmark/scenarios.ts b/apps/benchmarks/src/benchmark/scenarios.ts index 36cab63e..217f9ed2 100644 --- a/apps/benchmarks/src/benchmark/scenarios.ts +++ b/apps/benchmarks/src/benchmark/scenarios.ts @@ -1,5 +1,5 @@ import type { BenchmarkScenario } from './contracts'; -import { ADVANCED_SHAPING_CASES } from './advanced-shaping'; +import { ADVANCED_SHAPING_CASES } from '../workloads/advanced-shaping'; import paragraphBidiContract from '../../fixtures/contracts/paragraph-bidi-layout-v0.json'; import cjkContract from '../../fixtures/contracts/paragraph-cjk-layout-v0.json'; import cjkManifest from '../../fixtures/fonts/noto-sans-cjk-2.004/manifest.json'; diff --git a/apps/benchmarks/src/benchmark/target-boundary.test.ts b/apps/benchmarks/src/benchmark/target-boundary.test.ts index fb5c6b50..ddb15d50 100644 --- a/apps/benchmarks/src/benchmark/target-boundary.test.ts +++ b/apps/benchmarks/src/benchmark/target-boundary.test.ts @@ -38,11 +38,16 @@ describe('benchmark target boundaries', () => { it('resolves targets through the lazy group registry without importing the monolithic implementation facade', async () => { const registry = await readFile(new URL('./targets/registry.ts', import.meta.url), 'utf8'); const execution = await readFile(new URL('./execution.ts', import.meta.url), 'utf8'); + const conformance = await readFile(new URL('./targets/conformance/index.ts', import.meta.url), 'utf8'); expect(registry).toContain("import('./product')"); expect(registry).toContain("import('./measurement/font-baker')"); expect(registry).toContain("import('./conformance')"); expect(execution).toContain('await loadRegisteredTarget(request.targetId)'); + expect(conformance).toContain("import('./advanced-shaping')"); + expect(conformance).not.toContain('renderer/advanced-shaping-conformance'); + const { createAdvancedShapingConformanceTarget } = await import('./targets/conformance/advanced-shaping'); + expect(createAdvancedShapingConformanceTarget().id).toBe('advanced-shaping-conformance'); expect(await loadRegisteredTarget('missing')).toBeUndefined(); }); diff --git a/apps/benchmarks/src/renderer/advanced-shaping-conformance.ts b/apps/benchmarks/src/benchmark/targets/conformance/advanced-shaping.ts similarity index 91% rename from apps/benchmarks/src/renderer/advanced-shaping-conformance.ts rename to apps/benchmarks/src/benchmark/targets/conformance/advanced-shaping.ts index 6439d2d3..113374ff 100644 --- a/apps/benchmarks/src/renderer/advanced-shaping-conformance.ts +++ b/apps/benchmarks/src/benchmark/targets/conformance/advanced-shaping.ts @@ -2,17 +2,17 @@ import { FontRegistry, Text, type RegisteredFont } from '@pmndrs/text'; import { bitmap } from '@pmndrs/text/raster/bitmap'; import * as THREE from 'three/webgpu'; -import amiriBitmapFontUrl from '../../fixtures/rendering/amiri-bitmap-16.font.glb?url'; -import notoCjkShowcaseBitmapFontUrl from '../../fixtures/rendering/noto-sans-cjk-showcase-bitmap-16.font.glb?url'; -import interBitmapFontUrl from '../../fixtures/rendering/inter-bitmap-16.font.glb?url'; -import devanagariBitmapFontUrl from '../../fixtures/rendering/noto-sans-devanagari-bitmap-16.font.glb?url'; +import amiriBitmapFontUrl from '../../../../fixtures/rendering/amiri-bitmap-16.font.glb?url'; +import notoCjkShowcaseBitmapFontUrl from '../../../../fixtures/rendering/noto-sans-cjk-showcase-bitmap-16.font.glb?url'; +import interBitmapFontUrl from '../../../../fixtures/rendering/inter-bitmap-16.font.glb?url'; +import devanagariBitmapFontUrl from '../../../../fixtures/rendering/noto-sans-devanagari-bitmap-16.font.glb?url'; import { ADVANCED_SHAPING_CASES, advancedShapingFrames, type AdvancedShapingFontFixture, -} from '../benchmark/advanced-shaping'; -import type { BenchmarkTarget } from '../benchmark/contracts'; -import { hashParagraphLayout, paragraphLayoutBytes } from '../benchmark/paragraph-layout-digest'; +} from '../../../workloads/advanced-shaping'; +import type { BenchmarkTarget } from '../../contracts'; +import { hashParagraphLayout, paragraphLayoutBytes } from '../../paragraph-layout-digest'; const VIEWPORT_WIDTH = 800; const FONT_SIZE = 16; diff --git a/apps/benchmarks/src/benchmark/targets/conformance/index.ts b/apps/benchmarks/src/benchmark/targets/conformance/index.ts index 5b1401d0..29a2f15e 100644 --- a/apps/benchmarks/src/benchmark/targets/conformance/index.ts +++ b/apps/benchmarks/src/benchmark/targets/conformance/index.ts @@ -63,8 +63,7 @@ const advancedShapingTarget = () => capabilities: rasterCapabilities, status: () => 'ready', }, - async () => - (await import('../../../renderer/advanced-shaping-conformance')).createAdvancedShapingConformanceTarget(), + async () => (await import('./advanced-shaping')).createAdvancedShapingConformanceTarget(), ); function sourceOutlineFidelityTarget(technique: Technique, backend: Backend): BenchmarkTarget { diff --git a/apps/benchmarks/src/components/presentation-control-dock.tsx b/apps/benchmarks/src/components/presentation-control-dock.tsx index 0981301d..31ee16e0 100644 --- a/apps/benchmarks/src/components/presentation-control-dock.tsx +++ b/apps/benchmarks/src/components/presentation-control-dock.tsx @@ -21,7 +21,7 @@ import { type AdvancedShapingCommand, type AdvancedShapingFrame, type AdvancedShapingState, -} from '../benchmark/advanced-shaping'; +} from '../workloads/advanced-shaping'; /* * The dock intentionally consumes the same typed control state as the main panel. * shadcn/Base UI owns interaction semantics; this component only maps state to a compact presentation. diff --git a/apps/benchmarks/src/components/render-controls.tsx b/apps/benchmarks/src/components/render-controls.tsx index 3d7f1593..527340c0 100644 --- a/apps/benchmarks/src/components/render-controls.tsx +++ b/apps/benchmarks/src/components/render-controls.tsx @@ -5,7 +5,7 @@ import { type AdvancedShapingCommand, type AdvancedShapingFrame, type AdvancedShapingState, -} from '../benchmark/advanced-shaping'; +} from '../workloads/advanced-shaping'; import { BENCHMARK_FONT_LABELS, ICON_GRID_FONT_FIXTURE, diff --git a/apps/benchmarks/src/components/workload-rail.tsx b/apps/benchmarks/src/components/workload-rail.tsx index ade381af..8c96f6fd 100644 --- a/apps/benchmarks/src/components/workload-rail.tsx +++ b/apps/benchmarks/src/components/workload-rail.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from 'react'; -import type { AdvancedShapingFrame } from '../benchmark/advanced-shaping'; +import type { AdvancedShapingFrame } from '../workloads/advanced-shaping'; import { ADVANCED_FONT_FIXTURES, BENCHMARK_FONT_LABELS, diff --git a/apps/benchmarks/src/renderer/comparison-workload.ts b/apps/benchmarks/src/renderer/comparison-workload.ts index b66958b0..d1a19a1e 100644 --- a/apps/benchmarks/src/renderer/comparison-workload.ts +++ b/apps/benchmarks/src/renderer/comparison-workload.ts @@ -10,7 +10,7 @@ import { comparisonWorkloadRequiresIconWindowSuspension as registryRequiresIconWindowSuspension, comparisonWorkloadUpdateKind as registryUpdateKind, } from '../workloads/registry'; -import { DYNAMIC_LAYOUT_TEXT, animateDynamicLayoutEntries, dynamicLayoutWidths } from '../workloads/dynamic-layout'; +import { DYNAMIC_LAYOUT_TEXT, dynamicLayoutWidths } from '../workloads/dynamic-layout'; import { ICON_GRID_ITEMS, ICON_GRID_LABEL_SIZE, @@ -33,14 +33,14 @@ import { type IconGridLayout, type IconGridVirtualWindow, } from '../workloads/icon-grid'; -import { animateOffAxis3dEntries } from '../workloads/off-axis-3d'; -import { animatePaintEffectsEntries, applyPaintEffectsRetainedConfiguration } from '../workloads/paint-effects'; -import { animateParagraphStressScene } from '../workloads/paragraph-stress'; -import { animateTextLadderScene } from '../workloads/text-ladder'; import type { MutableTextLadderScenePosition } from '../workloads/text-ladder'; -import { ZOOM_TEXT_BASE_CSS_PX, animateZoomTextEntries } from '../workloads/zoom-text'; +import { ZOOM_TEXT_BASE_CSS_PX } from '../workloads/zoom-text'; import type { ZoomTextAnimationState } from '../workloads/zoom-text'; -import type { ComparisonWorkloadConfiguration, ComparisonWorkloadId } from '../workloads/contracts'; +import type { + ComparisonWorkloadAnimationScratch, + ComparisonWorkloadConfiguration, + ComparisonWorkloadId, +} from '../workloads/contracts'; import { committedTextLayout, type ComparisonWorkloadEntry } from '../workloads/factory-contracts'; import { loadBitmapFont, registeredBitmapAtlas, type BitmapTextLiveStats } from './bitmap-text'; import { createCanvasSurface } from './canvas-surface'; @@ -401,6 +401,11 @@ async function createComparisonWorkloadRuntime( const zoomAnimationState: ZoomTextAnimationState = { phraseIndex: 0, phraseRevision: 0, progress: 0 }; const textLadderPositionScratch: MutableTextLadderScenePosition = { x: 0, y: 0 }; const dynamicWidthsScratch = new Float64Array(DYNAMIC_LAYOUT_TEXT.length); + const workloadAnimationScratch: ComparisonWorkloadAnimationScratch = { + dynamicWidths: dynamicWidthsScratch, + textLadderPosition: textLadderPositionScratch, + zoomText: zoomAnimationState, + }; const iconAutoPanState: IconGridAutoPanState = { directionX: 1, directionY: 1, scrollX: 0, scrollY: 0 }; const iconFrameDeltaState: IconGridFrameDeltaState = { smoothedElapsedMs: undefined }; let iconAutoPanTimestamp: number | undefined; @@ -1064,37 +1069,34 @@ async function createComparisonWorkloadRuntime( if ( renderScene && !fontFixtureSwitching && - configuration.workload === 'text-ladder' && - configuration.animationEnabled + (configuration.workload === 'text-ladder' || configuration.workload === 'paragraph-stress') ) { - animateTextLadderScene( - scene, + animateEntries( entries, configuration, Math.max(0, timestamp - animationEpoch), width, height, - textLadderPositionScratch, + scene, + workloadAnimationScratch, + onError, + recordReflow, ); } - if ( - renderScene && - !fontFixtureSwitching && - configuration.workload === 'paragraph-stress' && - configuration.animationEnabled - ) { - animateParagraphStressScene(scene, entries, configuration, Math.max(0, timestamp - animationEpoch), height); - } if (renderScene && !fontFixtureCommitting) { - if (!fontFixtureSwitching) { + if ( + !fontFixtureSwitching && + configuration.workload !== 'text-ladder' && + configuration.workload !== 'paragraph-stress' + ) { animateEntries( entries, configuration, Math.max(0, timestamp - animationEpoch), - zoomAnimationState, - dynamicWidthsScratch, width, height, + scene, + workloadAnimationScratch, onError, recordReflow, ); @@ -1422,38 +1424,25 @@ function layoutEntries( function animateEntries( entries: readonly WorkloadEntry[], configuration: ComparisonWorkloadConfiguration, - timestamp: number, - zoomAnimationState: ZoomTextAnimationState, - dynamicWidthsScratch: Float64Array, + elapsedMs: number, width: number, height: number, + scene: THREE.Scene, + scratch: ComparisonWorkloadAnimationScratch, onError: (error: unknown) => void, onReflow: (duration: number) => void, ): void { - if (configuration.workload === 'zoom-text') { - animateZoomTextEntries(entries, configuration, timestamp, zoomAnimationState); - return; - } - if (configuration.workload === 'paint-effects') { - animatePaintEffectsEntries(entries, configuration, timestamp); - return; - } - if (configuration.workload === 'dynamic-layout') { - animateDynamicLayoutEntries( - entries, - configuration, - timestamp, - width, - height, - dynamicWidthsScratch, - onError, - onReflow, - ); - return; - } - if (configuration.workload === 'off-axis-3d') { - animateOffAxis3dEntries(entries, configuration, timestamp); - } + comparisonWorkloadDefinition(configuration.workload).animate( + entries, + configuration, + elapsedMs, + width, + height, + scene, + scratch, + onError, + onReflow, + ); } function applyRetainedConfiguration( @@ -1461,11 +1450,7 @@ function applyRetainedConfiguration( technique: RasterTechnique, configuration: ComparisonWorkloadConfiguration, ): void { - for (const entry of entries) { - if (entry.bounds !== undefined) entry.bounds.visible = configuration.showLayoutBounds; - } - if (configuration.workload !== 'paint-effects') return; - applyPaintEffectsRetainedConfiguration(entries, technique, configuration); + comparisonWorkloadDefinition(configuration.workload).applyRetainedConfiguration(entries, configuration, technique); } export function comparisonWorkloadUpdateKind( diff --git a/apps/benchmarks/src/renderer/mtsdf-text.ts b/apps/benchmarks/src/renderer/mtsdf-text.ts index c56009e9..ec8cdb87 100644 --- a/apps/benchmarks/src/renderer/mtsdf-text.ts +++ b/apps/benchmarks/src/renderer/mtsdf-text.ts @@ -14,7 +14,7 @@ import showcaseManifest from '../../fixtures/rendering/showcase-mtsdf-fixtures-v import { conformanceText, type BenchmarkFontFixture, type SelectableFontFixture } from '../benchmark/font-fixtures'; import type { BenchmarkTarget, TargetRunOutput } from '../benchmark/contracts'; import type { FontDelivery } from '../benchmark/url-state'; -import { BENCHMARK_IPSUM_CONFORMANCE_TEXT } from '../benchmark/benchmark-ipsum'; +import { BENCHMARK_IPSUM_CONFORMANCE_TEXT } from '../workloads/benchmark-ipsum'; import { createCanvasSurface, type CanvasSurface } from './canvas-surface'; import { finiteCanvasDelta } from './canvas-view'; import type { LiveFrameHistoryCursor } from './live-frame-telemetry'; diff --git a/apps/benchmarks/src/renderer/slug-text.ts b/apps/benchmarks/src/renderer/slug-text.ts index de1c8032..6e61c4bb 100644 --- a/apps/benchmarks/src/renderer/slug-text.ts +++ b/apps/benchmarks/src/renderer/slug-text.ts @@ -23,7 +23,7 @@ import devanagariCompressedFontUrl from '../../fixtures/rendering/noto-sans-deva import notoCjkShowcaseCompressedFontUrl from '../../fixtures/rendering/noto-sans-cjk-showcase-slug.font.glb.gz?url'; import sourceSerifCompressedFontUrl from '../../fixtures/rendering/source-serif-4-slug.font.glb.gz?url'; import showcaseManifest from '../../fixtures/rendering/showcase-slug-fixtures-v0.json'; -import { BENCHMARK_IPSUM_CONFORMANCE_TEXT } from '../benchmark/benchmark-ipsum'; +import { BENCHMARK_IPSUM_CONFORMANCE_TEXT } from '../workloads/benchmark-ipsum'; import type { BenchmarkTarget, TargetRunOutput } from '../benchmark/contracts'; import { rasterConformanceSpecimen, type BenchmarkFontFixture } from '../benchmark/font-fixtures'; import type { FontDelivery } from '../benchmark/url-state'; diff --git a/apps/benchmarks/src/benchmark/advanced-shaping.test.ts b/apps/benchmarks/src/workloads/advanced-shaping.test.ts similarity index 97% rename from apps/benchmarks/src/benchmark/advanced-shaping.test.ts rename to apps/benchmarks/src/workloads/advanced-shaping.test.ts index 2e39e189..15532d97 100644 --- a/apps/benchmarks/src/benchmark/advanced-shaping.test.ts +++ b/apps/benchmarks/src/workloads/advanced-shaping.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import type { BenchmarkMeasurement } from './contracts'; -import { scenarioById } from './scenarios'; +import type { BenchmarkMeasurement } from '../benchmark/contracts'; +import { scenarioById } from '../benchmark/scenarios'; import { ADVANCED_SHAPING_CASES, @@ -14,7 +14,7 @@ import { updateAdvancedShaping, } from './advanced-shaping'; -describe('advanced-shaping timeline', () => { +describe('Advanced Shaping workload timeline', () => { it('starts the live showcase on CJK without publishing an empty playing frame', () => { expect(initialAdvancedShapingState()).toMatchObject({ playing: true, diff --git a/apps/benchmarks/src/benchmark/advanced-shaping.ts b/apps/benchmarks/src/workloads/advanced-shaping.ts similarity index 99% rename from apps/benchmarks/src/benchmark/advanced-shaping.ts rename to apps/benchmarks/src/workloads/advanced-shaping.ts index 33f59573..756c06ea 100644 --- a/apps/benchmarks/src/benchmark/advanced-shaping.ts +++ b/apps/benchmarks/src/workloads/advanced-shaping.ts @@ -1,3 +1,4 @@ +/** Authored Advanced Shaping workload state and timeline. */ import { graphemeSegments } from 'unicode-segmenter/grapheme'; export type AdvancedShapingCaseId = diff --git a/apps/benchmarks/src/benchmark/benchmark-ipsum.test.ts b/apps/benchmarks/src/workloads/benchmark-ipsum.test.ts similarity index 96% rename from apps/benchmarks/src/benchmark/benchmark-ipsum.test.ts rename to apps/benchmarks/src/workloads/benchmark-ipsum.test.ts index 74c4c3f4..cfd4ad2d 100644 --- a/apps/benchmarks/src/benchmark/benchmark-ipsum.test.ts +++ b/apps/benchmarks/src/workloads/benchmark-ipsum.test.ts @@ -8,7 +8,7 @@ import { BENCHMARK_IPSUM_TEXT, } from './benchmark-ipsum'; -describe('benchmark ipsum', () => { +describe('Benchmark Ipsum workload', () => { it('keeps five independently diagnosable conformance lanes', () => { expect(BENCHMARK_IPSUM_CONFORMANCE_LINES).toHaveLength(5); expect(BENCHMARK_IPSUM_CONFORMANCE_TEXT).toBe(BENCHMARK_IPSUM_CONFORMANCE_LINES.join('\n')); diff --git a/apps/benchmarks/src/benchmark/benchmark-ipsum.ts b/apps/benchmarks/src/workloads/benchmark-ipsum.ts similarity index 97% rename from apps/benchmarks/src/benchmark/benchmark-ipsum.ts rename to apps/benchmarks/src/workloads/benchmark-ipsum.ts index d439c5d5..16e47560 100644 --- a/apps/benchmarks/src/benchmark/benchmark-ipsum.ts +++ b/apps/benchmarks/src/workloads/benchmark-ipsum.ts @@ -1,5 +1,5 @@ /** - * Stable product-demo corpus for native-strike rendering checks. + * Stable Benchmark Ipsum workload corpus for native-strike rendering checks. * * Keep each concern on its own line so a visual failure can be localized without * changing the corpus. Missing fixture coverage is reported rather than hidden by diff --git a/apps/benchmarks/src/workloads/contracts.ts b/apps/benchmarks/src/workloads/contracts.ts index c9be010e..69c3f0e9 100644 --- a/apps/benchmarks/src/workloads/contracts.ts +++ b/apps/benchmarks/src/workloads/contracts.ts @@ -1,4 +1,5 @@ import type { AnyRasterInput, RegisteredFont } from '@pmndrs/text'; +import type * as THREE from 'three/webgpu'; import type { RasterConformanceSpecimen, BenchmarkFontFixture } from '../benchmark/font-fixtures'; import type { RasterTechnique } from '../benchmark/url-state'; @@ -56,6 +57,13 @@ export interface ComparisonWorkloadCreateContext extends ComparisonWorkloadLayou readonly textLadderSpecimen?: RasterConformanceSpecimen; } +/** Reused host-owned scratch storage exposed to workload frame hooks without per-frame allocation. */ +export interface ComparisonWorkloadAnimationScratch { + readonly dynamicWidths: Float64Array; + readonly textLadderPosition: { x: number; y: number }; + readonly zoomText: { phraseIndex: number; phraseRevision: number; progress: number }; +} + /** * App-private workload policy and scene hooks. The retained host remains responsible for renderer, * scene activation, cancellation, telemetry, and transactional Text publication. @@ -67,6 +75,22 @@ export interface ComparisonWorkloadDefinition { readonly suspendsIconWindow: boolean; create(context: ComparisonWorkloadCreateContext): readonly ComparisonWorkloadEntry[]; layout(entries: readonly ComparisonWorkloadEntry[], context: ComparisonWorkloadLayoutContext): void; + animate( + entries: readonly ComparisonWorkloadEntry[], + configuration: ComparisonWorkloadConfiguration, + elapsedMs: number, + viewportWidth: number, + viewportHeight: number, + scene: THREE.Scene, + scratch: ComparisonWorkloadAnimationScratch, + onError: (error: unknown) => void, + onReflow: (duration: number) => void, + ): void; + applyRetainedConfiguration( + entries: readonly ComparisonWorkloadEntry[], + configuration: ComparisonWorkloadConfiguration, + technique: RasterTechnique, + ): void; updateKind( previous: ComparisonWorkloadConfiguration, next: ComparisonWorkloadConfiguration, diff --git a/apps/benchmarks/src/workloads/dynamic-layout.ts b/apps/benchmarks/src/workloads/dynamic-layout.ts index 53785a23..12b2cb25 100644 --- a/apps/benchmarks/src/workloads/dynamic-layout.ts +++ b/apps/benchmarks/src/workloads/dynamic-layout.ts @@ -17,6 +17,23 @@ export const DYNAMIC_LAYOUT_TEXT = [ ] as const; export const dynamicLayoutWorkload = { + animate(entries, configuration, elapsedMs, viewportWidth, viewportHeight, _scene, scratch, onError, onReflow) { + animateDynamicLayoutEntries( + entries, + configuration, + elapsedMs, + viewportWidth, + viewportHeight, + scratch.dynamicWidths, + onError, + onReflow, + ); + }, + applyRetainedConfiguration(entries, configuration) { + for (const entry of entries) { + if (entry.bounds !== undefined) entry.bounds.visible = configuration.showLayoutBounds; + } + }, cameraKind: 'orthographic', contentWidth: { maximumWidth: 1_000 }, create(context) { diff --git a/apps/benchmarks/src/workloads/icon-grid.ts b/apps/benchmarks/src/workloads/icon-grid.ts index 59fc3a22..60190381 100644 --- a/apps/benchmarks/src/workloads/icon-grid.ts +++ b/apps/benchmarks/src/workloads/icon-grid.ts @@ -29,6 +29,8 @@ const ICON_GRID_CONTENT = ICON_GRID_ITEMS.map((icon) => { /** Icon Grid keeps a retained, virtualized pool and therefore owns suspension policy. */ export const iconGridWorkload = { + animate() {}, + applyRetainedConfiguration() {}, cameraKind: 'orthographic', contentWidth: 'none', create(context) { diff --git a/apps/benchmarks/src/workloads/off-axis-3d.ts b/apps/benchmarks/src/workloads/off-axis-3d.ts index f091c17a..39eca202 100644 --- a/apps/benchmarks/src/workloads/off-axis-3d.ts +++ b/apps/benchmarks/src/workloads/off-axis-3d.ts @@ -31,6 +31,10 @@ export const OFF_AXIS_SPANS: readonly MutablePaintSpan[] = OFF_AXIS_WORD_COLORS. const colorAt = createOklabColorCycle(OFF_AXIS_WORD_COLORS.map(({ color }) => color)); export const offAxis3dWorkload = { + animate(entries, configuration, elapsedMs) { + animateOffAxis3dEntries(entries, configuration, elapsedMs); + }, + applyRetainedConfiguration() {}, cameraKind: 'perspective', contentWidth: { multiplier: 2 }, create(context) { diff --git a/apps/benchmarks/src/workloads/paint-effects.ts b/apps/benchmarks/src/workloads/paint-effects.ts index 8844f141..5013fb0e 100644 --- a/apps/benchmarks/src/workloads/paint-effects.ts +++ b/apps/benchmarks/src/workloads/paint-effects.ts @@ -23,6 +23,12 @@ const PAINT_WORD_RANGES = Array.from(PAINT_EFFECTS_TEXT.matchAll(/\S+/g), (match })); export const paintEffectsWorkload = { + animate(entries, configuration, elapsedMs) { + animatePaintEffectsEntries(entries, configuration, elapsedMs); + }, + applyRetainedConfiguration(entries, configuration, technique) { + applyPaintEffectsRetainedConfiguration(entries, technique, configuration); + }, cameraKind: 'orthographic', contentWidth: {}, create(context) { diff --git a/apps/benchmarks/src/workloads/paragraph-stress.ts b/apps/benchmarks/src/workloads/paragraph-stress.ts index aac3897e..fbc52afa 100644 --- a/apps/benchmarks/src/workloads/paragraph-stress.ts +++ b/apps/benchmarks/src/workloads/paragraph-stress.ts @@ -12,6 +12,11 @@ import { } from './factory-contracts'; export const paragraphStressWorkload = { + animate(entries, configuration, elapsedMs, _viewportWidth, viewportHeight, scene) { + if (!configuration.animationEnabled) return; + animateParagraphStressScene(scene, entries, configuration, elapsedMs, viewportHeight); + }, + applyRetainedConfiguration() {}, cameraKind: 'orthographic', contentWidth: {}, create(context) { diff --git a/apps/benchmarks/src/workloads/registry.test.ts b/apps/benchmarks/src/workloads/registry.test.ts index d5844011..bc270120 100644 --- a/apps/benchmarks/src/workloads/registry.test.ts +++ b/apps/benchmarks/src/workloads/registry.test.ts @@ -22,6 +22,8 @@ describe('comparison workload registry', () => { expect(definition.id).toBe(id); expect(['orthographic', 'perspective']).toContain(definition.cameraKind); expect(definition.contentWidth === 'none' || typeof definition.contentWidth === 'object').toBe(true); + expect(typeof definition.animate).toBe('function'); + expect(typeof definition.applyRetainedConfiguration).toBe('function'); expect(typeof definition.create).toBe('function'); expect(typeof definition.layout).toBe('function'); expect(typeof definition.updateKind).toBe('function'); diff --git a/apps/benchmarks/src/workloads/text-ladder.ts b/apps/benchmarks/src/workloads/text-ladder.ts index 1173fb76..50891c5b 100644 --- a/apps/benchmarks/src/workloads/text-ladder.ts +++ b/apps/benchmarks/src/workloads/text-ladder.ts @@ -23,6 +23,19 @@ export interface MutableTextLadderScenePosition { } export const textLadderWorkload = { + animate(entries, configuration, elapsedMs, viewportWidth, viewportHeight, scene, scratch) { + if (!configuration.animationEnabled) return; + animateTextLadderScene( + scene, + entries, + configuration, + elapsedMs, + viewportWidth, + viewportHeight, + scratch.textLadderPosition, + ); + }, + applyRetainedConfiguration() {}, cameraKind: 'orthographic', contentWidth: 'none', create(context) { diff --git a/apps/benchmarks/src/workloads/zoom-text.ts b/apps/benchmarks/src/workloads/zoom-text.ts index cf38ae55..b2386db9 100644 --- a/apps/benchmarks/src/workloads/zoom-text.ts +++ b/apps/benchmarks/src/workloads/zoom-text.ts @@ -41,6 +41,10 @@ export interface ZoomTextAnimationState { } export const zoomTextWorkload = { + animate(entries, configuration, elapsedMs, _viewportWidth, _viewportHeight, _scene, scratch) { + animateZoomTextEntries(entries, configuration, elapsedMs, scratch.zoomText); + }, + applyRetainedConfiguration() {}, cameraKind: 'orthographic', contentWidth: 'none', create(context) { diff --git a/apps/benchmarks/vitexec/advanced-shaping-performance.probe.ts b/apps/benchmarks/vitexec/advanced-shaping-performance.probe.ts index 374f2287..468c9b09 100644 --- a/apps/benchmarks/vitexec/advanced-shaping-performance.probe.ts +++ b/apps/benchmarks/vitexec/advanced-shaping-performance.probe.ts @@ -1,4 +1,4 @@ -const advancedShapingPath = '/src/benchmark/advanced-shaping.ts'; +const advancedShapingPath = '/src/workloads/advanced-shaping.ts'; const environmentPath = '/src/benchmark/environment.ts'; const STEADY_STATE_REPORT_COUNT = 12; const [{ ADVANCED_SHAPING_CASES }, { environmentResource }] = await Promise.all([ diff --git a/apps/benchmarks/vitexec/core-text-frame.probe.ts b/apps/benchmarks/vitexec/core-text-frame.probe.ts index 9c8bb399..9fdd38a7 100644 --- a/apps/benchmarks/vitexec/core-text-frame.probe.ts +++ b/apps/benchmarks/vitexec/core-text-frame.probe.ts @@ -2,7 +2,7 @@ import { FontRegistry, Text } from '@pmndrs/text'; import { bitmap } from '@pmndrs/text/raster/bitmap'; import * as THREE from 'three/webgpu'; -const benchmarkIpsumPath = '/src/benchmark/benchmark-ipsum.ts'; +const benchmarkIpsumPath = '/src/workloads/benchmark-ipsum.ts'; const tslBaselinePath = '/src/renderer/tsl-baseline.ts'; const rendererPath = '/src/renderer/webgpu-renderer.ts'; const [{ BENCHMARK_IPSUM_CONFORMANCE_TEXT }, { compactRgba8Readback }, { createConfiguredRenderer }] = diff --git a/apps/benchmarks/vitexec/harness.probe.ts b/apps/benchmarks/vitexec/harness.probe.ts index 66230431..9349e4cc 100644 --- a/apps/benchmarks/vitexec/harness.probe.ts +++ b/apps/benchmarks/vitexec/harness.probe.ts @@ -247,7 +247,7 @@ await waitForElementState('[data-testid="comparison-live-viewport"]', { 'data-technique': 'bitmap', }); -const advancedShapingPath = '/src/benchmark/advanced-shaping.ts'; +const advancedShapingPath = '/src/workloads/advanced-shaping.ts'; const { ADVANCED_SHAPING_CASES } = await import(/* @vite-ignore */ advancedShapingPath); const advancedShapingButton = [...document.querySelectorAll('button')].find( (candidate) => candidate.textContent?.includes('Advanced shaping') === true && !candidate.disabled, diff --git a/docs/log.md b/docs/log.md index 7fdd9470..198bd013 100644 --- a/docs/log.md +++ b/docs/log.md @@ -2,6 +2,7 @@ ## 2026-08-03 +- **Complete workload phase ownership** — Added typed animation and retained-configuration hooks to all seven retained workload definitions using preallocated host scratch, eliminating the renderer's remaining create/layout/animate/apply dispatch switches without moving RAF, telemetry, or renderer lifecycle into workload code. Moved the Benchmark Ipsum corpus and Advanced Shaping timeline beside the other authored workloads, and moved the self-contained Advanced Shaping conformance target under `benchmark/targets/conformance` behind the same literal selected-target dynamic import. Coupled raster targets remain in place pending a session adapter that preserves their warm load/run/dispose lifecycle. The post-move Chromium run completed all 42 dual-backend Presentation workload cells with visible pixels and one renderer per lane; all 19 isolated headless conformance scenarios passed, including three exact 68-frame Advanced Shaping timelines with zero warm readiness waits. - **Workload controller boundary** — Made all seven retained workload definitions own typed app-private construction and layout hooks, so the renderer selects those behaviors only through the exhaustive registry. Moved Icon Grid layout, virtual-window calculation, assignment validation, recycled-entry positioning, pan integration, and frame-delta smoothing beside its canonical public `Text` example. Main and Presentation now derive descriptions, amount labels, fixed/selectable font behavior, preload grouping, and pan/zoom capability from the typed catalog instead of duplicating workload-ID policy. Renderer, canvas, RAF, GPU timer, telemetry, font transactions, active pool ownership, and teardown remain host infrastructure; no public package API was added. Chromium 149 completed all 42 Bitmap/MTSDF/Slug × WebGPU/WebGL2 × seven-workload Presentation cells with visible pixels, one renderer per lane, and no reported console failure. - **Workload scene and conformance locality** — Moved Text Ladder, Zoom Text, Off-axis / 3D, Dynamic Layout, Paragraph Stress, and Paint & Effects layout and animation behavior beside their public `Text` constructors, leaving the retained renderer as a dispatcher and Icon Grid virtualization as the next controller extraction. Extracted the conformance React hierarchy from the root application while preserving one host-owned renderer for retained comparisons and exclusive finite captures. A deterministic delayed-peer regression proved the realtime MSDF / Slug comparison could refresh one candidate generation early; the private scene now retains the last complete target pair, defers target resize, publishes both retained objects in one task, rolls both back on failure, and drains an in-flight pair before disposal. Seven focused lifecycle cases cover renderer-state restoration, success, failure, abort, partial readiness, rollback, and delayed resize without admitting a renderer-wide grouped-publication API. - **Benchmark workload policy and target ownership** — Added one typed catalog for all nine live benchmark workloads, including exact Main/Presentation reset defaults, font policy, controls, interaction, preload, and surface kind. Benchmark rail metadata and runtime resets project from that authority, and URL parsing normalizes unknown workload IDs inside the selected mode before downstream policy executes. Moved technique-invariant text styling and color motion below `workloads/shared` and added a static/dynamic source boundary that rejects workload dependencies on renderer implementation modules. Reclassified public loader/Worker parity as conformance, moved direct Wasm dependencies into one shared selected-ABI adapter, and retained boundary tests that reject raw Wasm imports elsewhere. diff --git a/docs/packages/benchmarks.md b/docs/packages/benchmarks.md index 3ad78c2a..89d17996 100644 --- a/docs/packages/benchmarks.md +++ b/docs/packages/benchmarks.md @@ -5,7 +5,7 @@ description: Provides the shared interactive and automated benchmark product sur resource: ../../apps/benchmarks workspace_package: '@pmndrs/text-benchmarks' documentation_type: reference -source_digest: 'sha256:efcc04f561fb0f5580962aa34941ecff31f01398ed12f8809095e01cffc71a15' +source_digest: 'sha256:e6e8fa72d40f4ad578ed8c640a82a363419993dbdcd7b19ed39ba425bdc8dfc2' tags: [package, benchmarks, react, vite, product-e2e] sources: - id: manifest @@ -18,8 +18,11 @@ sources: resource: ../planning/benchmark-plan.md title: Benchmark plan - id: benchmark-ipsum - resource: ../../apps/benchmarks/src/benchmark/benchmark-ipsum.ts + resource: ../../apps/benchmarks/src/workloads/benchmark-ipsum.ts title: Canonical benchmark ipsum corpus + - id: advanced-shaping-workload + resource: ../../apps/benchmarks/src/workloads/advanced-shaping.ts + title: Authored Advanced Shaping workload timeline - id: slug-role-scenes resource: ../../apps/benchmarks/src/renderer/slug-role-scenes.ts title: Slug release-role scene definitions @@ -70,7 +73,7 @@ sources: title: Realtime comparison product probe generated: by: openai-codex/gpt-5.6 - at: '2026-08-03T08:25:13Z' + at: '2026-08-03T08:41:03Z' --- # Package reference: `@pmndrs/text-benchmarks` @@ -106,7 +109,7 @@ The Vite build runs the pinned React Compiler preset and emitted production bund Main and Presentation are exclusive URL-selected root presentations. Presentation owns the scene and floating chrome directly: it does not render Main's header, workload rail, control aside, compact sheets, navigation, or hidden Conformance Activity. Each route owns one persistent render host, canvas, animation loop, GPU timer, and telemetry ring for its backend generation. Workload and technique changes activate a replacement scene while the committed scene continues rendering, then atomically swap and dispose the old scene resources; compatible font changes update retained `Text` objects in place. React Suspense owns genuinely cold asset loading, while scene selections are committed with `useTransition` after preloading so warm transitions do not replace the visible scene or reset the graphs. Runtime diagnostics and the live workload probe require one active renderer, one active canvas, and a peak concurrency of one through rapid presentation, workload, and technique changes. Main enters Presentation through one accessible expand-corners icon rather than a text label. Presentation's workload, font, and shaping-case selectors use the official shadcn Base UI `Select`; Base UI owns portal placement, outside-press dismissal, Escape handling, focus restoration, and keyboard listbox behavior, while the application renders selected human labels explicitly instead of exposing stored keys. A fixed left-edge viewport slot top-aligns one compact shadcn `ButtonGroup`, so workload-specific controls grow downward or scroll without moving the dock anchor. Each dynamic value owns a shadcn `Popover`: global GPU/GL and DPR selectors, workload sliders and toggles, Advanced Shaping selection/text/timeline panels, and MTSDF-only stroke/shadow controls. Informational descriptions and controls unavailable to the selected technique do not enter the Presentation dock. MTSDF paint starts with zero stroke and shadow disabled. One 1.5× presentation-scale boundary enlarges the top controls, telemetry, dock, and payload labels; inverse-scale viewport constraints, group-specific transform origins, and a 32 CSS px safe area keep every floating group clear of host-frame corners and on-screen without overlap at 1,280×1,280. Presentation surfaces use restrained shadcn-radius corners, plain 80%-black composition without a backdrop filter, and opaque borders. The graph rail owns one continuous background with opaque dividers instead of exposing translucent seams between child charts. Canvas render captions and navigation status remain Main-only. No visible exit action competes with the compact top-right telemetry stack, and Escape returns to Main. The comparison workloads retain framework-neutral `Text` objects behind the active presentation. Paint & Effects advances one circular per-word chromatic sequence directly on the renderer RAF through the synchronous paint-only batch path; shaping, paragraph layout, geometry, and React do not drive individual color frames. Its source span topology and update object are retained across animation frames, while `Text` retains the glyph-to-paint index plan. Animate, speed, hue, opacity, shadow, stroke, and layout-bounds controls mutate retained scene state and never enter the scene-rebuild path. Ordinary font-size and paragraph-volume changes may replace a generation because they alter glyph geometry or authored text. Resident layout-width, compatible viewport-width, font-size, font-fixture, and Dynamic Layout changes stage every affected `Text`, publish through Three.js matrix traversal, and reposition only after the complete synchronous lifecycle publication; `ready` remains a cold/error observation channel rather than warm control flow. Rapid controls pass through a latest-value serialized drain so obsolete intermediate values are never staged.[^comparison-workload] Live controls invalidate only an explicit capture, not the continuously updated typed-array telemetry, so metric labels and graphs do not disappear while a slider moves. Font selection retains the active benchmark shell and its last telemetry rather than showing an intermediate empty metric frame; comparison workloads additionally retain their canvas while the next font prepares. The live probe observes causal, monotonically increasing paint revisions, rejects layout work or batch replacement during paint updates, and proves comparison-workload font switching preserves canvas identity and never empties the CPU metric. Dynamic Layout stages all three paragraph reflows as one batch and positions the complete trio after lifecycle publication; it never recenters a mixture of old and new layouts. Neutral inspection frames remain default-on and share the same typed configuration boundary. -Every live benchmark identity resolves through one typed catalog under `apps/benchmarks/src/workloads/`. The catalog owns labels, descriptions, exact Main and Presentation defaults, font policy, controls and ranges, pan/zoom capability, preload policy, and surface kind; URL parsing normalizes an unknown workload inside its selected mode before font or control policy executes. Main and Presentation derive scene descriptions, amount labels, font selection, preload grouping, and pan/zoom capability from that authority rather than repeating workload-ID switches. Text Ladder, Zoom Text, Icon Grid, Off-axis / 3D, Dynamic Layout, Paragraph Stress, and Paint & Effects each own their authored content and public `Text` construction. Their typed definition objects also own scene construction and layout; Text Ladder, Zoom Text, Off-axis / 3D, Dynamic Layout, Paragraph Stress, and Paint & Effects own animation behavior. Icon Grid owns its layout, virtual-window calculation, assignment validation, recycled-entry positioning, pan integration, and frame-delta smoothing, while the host still owns the active pool and telemetry until the instance-controller slice. Their technique-invariant content-width, text-style, and color-cycle utilities live below `workloads/shared`; a source-boundary test rejects static or dynamic imports from any workload module back into renderer implementation files. The route-owned renderer, RAF, telemetry, font delivery, transactional publication, and disposal remain benchmark infrastructure. Conformance React composition lives under `surfaces/conformance`: both the retained comparison and finite captures can receive only the host-owned renderer, while finite low-level dispatch stays below `benchmark/targets/conformance` and `benchmark/targets/measurement`. Those targets share the explicitly named `targets/shared/direct-wasm.ts` dependency adapter only after target selection. The public missing-sibling loader Worker is conformance because it proves authenticated Worker bytes and loader fallback behavior; it is not a rendering product target. Boundary tests reject workload imports back into renderer implementation and reject direct font-baker or Wasm URL imports outside the shared adapter, preventing raw tooling from leaking into the normal Presentation module graph. +Every live benchmark identity resolves through one typed catalog under `apps/benchmarks/src/workloads/`. The catalog owns labels, descriptions, exact Main and Presentation defaults, font policy, controls and ranges, pan/zoom capability, preload policy, and surface kind; URL parsing normalizes an unknown workload inside its selected mode before font or control policy executes. Main and Presentation derive scene descriptions, amount labels, font selection, preload grouping, and pan/zoom capability from that authority rather than repeating workload-ID switches. Benchmark Ipsum and Advanced Shaping now keep their authored corpus and timeline in the same workload hierarchy as Text Ladder, Zoom Text, Icon Grid, Off-axis / 3D, Dynamic Layout, Paragraph Stress, and Paint & Effects. The seven retained comparison definitions own construction, layout, animation, and retained configuration hooks; no workload-specific dispatch switch remains for those phases. Icon Grid owns its layout, virtual-window calculation, assignment validation, recycled-entry positioning, pan integration, and frame-delta smoothing, while the host still owns the active pool and telemetry until the instance-controller slice. Their technique-invariant content-width, text-style, and color-cycle utilities live below `workloads/shared`; a source-boundary test rejects static or dynamic imports from any workload module back into renderer implementation files. The route-owned renderer, RAF, telemetry, font delivery, transactional publication, and disposal remain benchmark infrastructure. Conformance React composition lives under `surfaces/conformance`: both the retained comparison and finite captures can receive only the host-owned renderer, while finite low-level dispatch stays below `benchmark/targets/conformance` and `benchmark/targets/measurement`. The self-contained Advanced Shaping conformance target now lives in that target hierarchy behind the registry's literal selected-target dynamic import; coupled raster targets remain behind narrow renderer sessions until their shared adapter contract can preserve warm `load → run → dispose` reuse. The targets share the explicitly named `targets/shared/direct-wasm.ts` dependency adapter only after target selection. The public missing-sibling loader Worker is conformance because it proves authenticated Worker bytes and loader fallback behavior; it is not a rendering product target. Boundary tests reject workload imports back into renderer implementation, authenticate the literal Advanced Shaping target import, and reject direct font-baker or Wasm URL imports outside the shared adapter, preventing raw tooling from leaking into the normal Presentation module graph. Timed playback compares each frame with the latest requested location rather than the last committed scene, so an in-flight preload receives exactly one request and cannot be superseded by a duplicate transition that skips workload-default initialization. Presentation captures Space at the window capture boundary to start or stop timed playback even while a button, switch, slider, select, or combobox owns focus; matching key-up activation is suppressed, while inputs, textareas, and editable text retain ordinary space entry. Arrow navigation remains disabled on interactive controls. diff --git a/docs/planning/benchmark-ipsum.md b/docs/planning/benchmark-ipsum.md index 0ed8dd95..53f5afba 100644 --- a/docs/planning/benchmark-ipsum.md +++ b/docs/planning/benchmark-ipsum.md @@ -6,7 +6,7 @@ status: stable tags: [benchmarks, corpus, shaping, typography] sources: - id: corpus - resource: ../../apps/benchmarks/src/benchmark/benchmark-ipsum.ts + resource: ../../apps/benchmarks/src/workloads/benchmark-ipsum.ts title: Executable benchmark ipsum corpus - id: benchmark-plan resource: benchmark-plan.md diff --git a/docs/roadmap/roadmap.md b/docs/roadmap/roadmap.md index a116c022..a3d2fa8f 100644 --- a/docs/roadmap/roadmap.md +++ b/docs/roadmap/roadmap.md @@ -687,7 +687,7 @@ Milestone 9 is closed. Additional Slug optimization hypotheses are future measur ## Milestone 10 — harden the first shippable release -Item 10.6 is active. Item 10.1 established the required renderer-neutral transaction and Three.js adapter parity, item 10.2 moved resident shaping, layout, paint planning, raster staging, and atomic publication into the Three.js object-update lifecycle without warm consumer readiness waits, item 10.3 added bounded retained instance capacity to all three first-party rasters, item 10.4 proved the published extension boundary with a private external consumer package, and item 10.5 removed benchmark workarounds and retained complete dual-backend Presentation evidence. The remaining accepted work is release review. Its current pass makes every live workload a readable consumer example with one typed default/control/font/surface policy, isolates low-level conformance targets, and records any concrete package escape hatch in the API fixture before proposing implementation. All seven retained workload definitions now own app-private construction and layout hooks, and Icon Grid owns its pure virtualization mechanics; moving animation, active pool state, and metrics behind workload instances remains in progress. A delayed-peer probe confirmed that the paired MSDF / Slug scene needs comparison-local target coordination; retaining the last complete target pair, publishing both objects in one task, and rolling back failures resolves it without a grouped public transaction. Each layer must remain independently green; renderer-wide batching across separate `Text` objects is explicitly not part of this milestone. +Item 10.6 is active. Item 10.1 established the required renderer-neutral transaction and Three.js adapter parity, item 10.2 moved resident shaping, layout, paint planning, raster staging, and atomic publication into the Three.js object-update lifecycle without warm consumer readiness waits, item 10.3 added bounded retained instance capacity to all three first-party rasters, item 10.4 proved the published extension boundary with a private external consumer package, and item 10.5 removed benchmark workarounds and retained complete dual-backend Presentation evidence. The remaining accepted work is release review. Its current pass makes every live workload a readable consumer example with one typed default/control/font/surface policy, isolates low-level conformance targets, and records any concrete package escape hatch in the API fixture before proposing implementation. All seven retained workload definitions now own app-private construction, layout, animation, and retained-configuration hooks; Icon Grid owns its pure virtualization mechanics, while moving active pool state and metrics behind one workload instance remains in progress. Benchmark Ipsum and Advanced Shaping authored state now live beside the other workloads, and the self-contained Advanced Shaping conformance target lives under low-level targets without changing selected-target chunking. A delayed-peer probe confirmed that the paired MSDF / Slug scene needs comparison-local target coordination; retaining the last complete target pair, publishing both objects in one task, and rolling back failures resolves it without a grouped public transaction. Each layer must remain independently green; renderer-wide batching across separate `Text` objects is explicitly not part of this milestone. ### 10.1–10.6 closure checklist