feat(dashboard): MSDF text engine — the all-WebGPU text linchpin

Day-1 linchpin of the zero-DOM Cognitive OS. Every label, receipt id, and
number in the dashboard now renders as glowing in-canvas MSDF glyphs instead
of DOM text. Checked-in JetBrains Mono mtsdf atlas (512x512, 95 glyphs) +
loader + pure layout helper + FramePass + trap-checked WGSL.

Built by a GPT-5.5 swarm against an adversarially-verified spec (5 verifiers
+ 3 skeptics caught 6 silent-fail traps before a line was written: V-flip,
release-binary atlas embed, base-path fetch, linear rgba8unorm, premultiplied
blend, ASCII-only charset). Conductor live-GPU audit on the real dashboard
found one more at runtime: the aspect transform pushed text off-screen in
portrait; fixed to keep glyphs square in both orientations.

Renders cyan, crisp, upright, glowing, glyph-by-glyph, landscape + portrait.
check 0 errors, build green, golden V-flip test 2/2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-07-09 12:43:58 -07:00
parent f167be2663
commit 389ddfa294
11 changed files with 1314 additions and 112 deletions

View file

@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import atlas from '../../../../static/msdf/jetbrains-mono.json';
import { layoutText, type MsdfAtlasJson } from '../text/layout';
describe('MSDF text layout', () => {
it('packs the atlas V flip for the top of A loudly', () => {
const glyphs = layoutText('A', atlas as MsdfAtlasJson);
expect(glyphs).toHaveLength(1);
// atlasBounds.top for A is 373.5 in the checked-in 512px atlas.
// WebGPU texture V is top-down, so the glyph top must sample 1 - top/512.
expect(glyphs[0].v).toBeCloseTo(1 - 373.5 / 512, 4);
expect(glyphs[0].v).toBeCloseTo(0.2705, 4);
});
it('falls back to ASCII question mark for non-atlas characters', () => {
const [fallback] = layoutText('·', atlas as MsdfAtlasJson);
const [question] = layoutText('?', atlas as MsdfAtlasJson);
expect(fallback).toEqual(question);
});
});

View file

@ -0,0 +1,146 @@
export interface MsdfBounds {
left: number;
bottom: number;
right: number;
top: number;
}
export interface MsdfGlyph {
unicode: number;
advance?: number;
planeBounds?: MsdfBounds;
atlasBounds?: MsdfBounds;
}
export interface MsdfAtlasJson {
atlas: {
width: number;
height: number;
};
metrics?: {
lineHeight?: number;
};
glyphs: MsdfGlyph[];
}
export interface GlyphInstance {
/** Quad origin in em/NDC-y units, baseline-relative, +Y up. */
x: number;
y: number;
/** Quad size in em/NDC-y units. */
w: number;
h: number;
/** Atlas UV origin. v is already flipped from atlas yOrigin: bottom to GPU top-down V. */
u: number;
v: number;
/** Atlas UV extent. vh is positive from flipped glyph top to flipped glyph bottom. */
uw: number;
vh: number;
}
export interface LayoutTextOptions {
/** Maximum line width in em units. Overlong lines are truncated with ASCII dots. */
maxWidthEm?: number;
/** Monospace advance in em units. The checked-in atlas uses 0.6 for every ASCII glyph. */
advance?: number;
/** Baseline-to-baseline distance in em units. Defaults to the atlas metric, then 1.32. */
lineHeight?: number;
}
const ASCII_MIN = 0x20;
const ASCII_MAX = 0x7e;
const FALLBACK_CODEPOINT = 0x3f; // '?'
const DEFAULT_ADVANCE = 0.6;
const DEFAULT_LINE_HEIGHT = 1.32;
const ELLIPSIS = '...';
function glyphMapFor(atlas: MsdfAtlasJson): Map<number, MsdfGlyph> {
return new Map(atlas.glyphs.map((glyph) => [glyph.unicode, glyph]));
}
function asciiFallback(char: string): string {
const codepoint = char.codePointAt(0) ?? FALLBACK_CODEPOINT;
if (codepoint >= ASCII_MIN && codepoint <= ASCII_MAX) {
return char;
}
return '?';
}
function truncateLine(line: string, maxChars: number | undefined): string {
if (maxChars === undefined || maxChars < 0) {
return line;
}
if (maxChars === 0) {
return '';
}
const chars = Array.from(line, asciiFallback);
if (chars.length <= maxChars) {
return chars.join('');
}
if (maxChars <= ELLIPSIS.length) {
return '.'.repeat(maxChars);
}
return `${chars.slice(0, maxChars - ELLIPSIS.length).join('')}${ELLIPSIS}`;
}
function preparedLines(text: string, maxChars: number | undefined): string[] {
return text.split('\n').map((line) => truncateLine(line, maxChars));
}
/**
* Layout ASCII-only MSDF glyph instances for the checked-in JetBrains Mono atlas.
*
* Coordinates are baseline-relative with +Y up. Atlas UVs are packed with the required
* V flip: atlas yOrigin is bottom, while GPU texture V is top-down. Newlines reset penX
* and move the baseline down by lineHeight. Advance is always 0.6em by default, even
* for spaces and glyphs without plane bounds.
*/
export function layoutText(text: string, atlas: MsdfAtlasJson, options: LayoutTextOptions = {}): GlyphInstance[] {
const glyphs = glyphMapFor(atlas);
const fallback = glyphs.get(FALLBACK_CODEPOINT);
const atlasWidth = atlas.atlas.width;
const atlasHeight = atlas.atlas.height;
const advance = options.advance ?? DEFAULT_ADVANCE;
const lineHeight = options.lineHeight ?? atlas.metrics?.lineHeight ?? DEFAULT_LINE_HEIGHT;
const maxChars = options.maxWidthEm === undefined ? undefined : Math.max(0, Math.floor(options.maxWidthEm / advance));
const instances: GlyphInstance[] = [];
let penY = 0;
for (const line of preparedLines(text, maxChars)) {
let penX = 0;
for (const rawChar of Array.from(line)) {
const char = asciiFallback(rawChar);
const codepoint = char.codePointAt(0) ?? FALLBACK_CODEPOINT;
const glyph = glyphs.get(codepoint) ?? fallback;
if (glyph?.planeBounds && glyph.atlasBounds) {
const plane = glyph.planeBounds;
const atlasBounds = glyph.atlasBounds;
const u = atlasBounds.left / atlasWidth;
const v = 1 - atlasBounds.top / atlasHeight;
const uw = (atlasBounds.right - atlasBounds.left) / atlasWidth;
const vh = 1 - atlasBounds.bottom / atlasHeight - v;
instances.push({
x: penX + plane.left,
y: penY + plane.bottom,
w: plane.right - plane.left,
h: plane.top - plane.bottom,
u,
v,
uw,
vh
});
}
penX += advance;
}
penY -= lineHeight;
}
return instances;
}
export const layoutMsdfText = layoutText;

View file

@ -0,0 +1,80 @@
import { base } from '$app/paths';
import type { MsdfGlyph } from './layout';
export type MsdfAtlasMetrics = {
emSize?: number;
lineHeight: number;
ascender?: number;
descender?: number;
underlineY?: number;
underlineThickness?: number;
};
export type LoadedMsdfAtlas = {
atlas: {
type?: string;
distanceRange: number;
distanceRangeMiddle?: number;
size: number;
width: number;
height: number;
yOrigin?: string;
};
metrics: MsdfAtlasMetrics;
glyphs: MsdfGlyph[];
glyphMap: Map<number, MsdfGlyph>;
texture: GPUTexture;
textureView: GPUTextureView;
sampler: GPUSampler;
dispose: () => void;
};
type RawAtlas = Omit<LoadedMsdfAtlas, 'glyphMap' | 'texture' | 'textureView' | 'sampler' | 'dispose'>;
export async function loadMsdfAtlas(device: GPUDevice): Promise<LoadedMsdfAtlas> {
const jsonUrl = `${base}/msdf/jetbrains-mono.json`;
const pngUrl = `${base}/msdf/jetbrains-mono.png`;
const jsonResponse = await fetch(jsonUrl);
if (!jsonResponse.ok) throw new Error(`MSDF atlas JSON failed: ${jsonResponse.status} ${jsonUrl}`);
const raw = (await jsonResponse.json()) as RawAtlas;
if (raw.atlas?.yOrigin !== 'bottom') {
throw new Error(`MSDF atlas yOrigin must be bottom, got ${raw.atlas?.yOrigin ?? 'missing'}`);
}
const pngResponse = await fetch(pngUrl);
if (!pngResponse.ok) throw new Error(`MSDF atlas PNG failed: ${pngResponse.status} ${pngUrl}`);
const blob = await pngResponse.blob();
const bitmap = await createImageBitmap(blob);
const texture = device.createTexture({
label: 'msdf-jetbrains-mono-rgba8unorm',
size: [bitmap.width, bitmap.height, 1],
format: 'rgba8unorm',
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
});
device.queue.copyExternalImageToTexture(
{ source: bitmap },
{ texture },
{ width: bitmap.width, height: bitmap.height }
);
bitmap.close?.();
const sampler = device.createSampler({
label: 'msdf-jetbrains-mono-linear-sampler',
magFilter: 'linear',
minFilter: 'linear',
mipmapFilter: 'linear',
addressModeU: 'clamp-to-edge',
addressModeV: 'clamp-to-edge'
});
const textureView = texture.createView({ label: 'msdf-jetbrains-mono-view' });
const glyphMap = new Map(raw.glyphs.map((glyph) => [glyph.unicode, glyph]));
return {
...raw,
glyphMap,
texture,
textureView,
sampler,
dispose: () => texture.destroy()
};
}

View file

@ -0,0 +1,91 @@
export const MSDF_TEXT_WGSL = /* wgsl */ `
struct Params {
frame: f32,
loop_phase: f32,
node_count: f32,
edge_count: f32,
path_count: f32,
pulse: f32,
viewport_w: f32,
viewport_h: f32,
brightness: f32,
demo_id: f32,
time: f32,
capture_mode: f32,
live_kind: f32,
live_frame: f32,
live_energy: f32,
projection_days: f32,
};
struct Glyph {
anchor_size: vec4f,
quad_offset: vec4f,
uv_rect: vec4f,
info: vec4f,
color: vec4f,
};
struct VSOut {
@builtin(position) clip: vec4f,
@location(0) uv: vec2f,
@location(1) @interpolate(flat) info: vec4f,
@location(2) @interpolate(flat) color: vec4f,
};
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read> glyphs: array<Glyph>;
@group(0) @binding(2) var atlas_sampler: sampler;
@group(0) @binding(3) var atlas_tex: texture_2d<f32>;
const QUAD = array<vec2f, 6>(
vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0),
vec2f(0.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 1.0)
);
fn median3(c: vec3f) -> f32 {
return max(min(c.r, c.g), min(max(c.r, c.g), c.b));
}
@vertex
fn vs_text(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut {
let glyph = glyphs[ii];
let corner = QUAD[vi];
let anchor = glyph.anchor_size.xy;
let size = glyph.anchor_size.zw;
let quad_offset = glyph.quad_offset.xy;
let uv_min = glyph.uv_rect.xy;
let uv_max = glyph.uv_rect.zw;
let aspect = max(0.0001, params.viewport_w / max(1.0, params.viewport_h));
var pos = anchor + quad_offset + corner * size;
// Keep glyphs square in BOTH orientations: normalize by the longer axis.
// Landscape (aspect>1): narrow x. Portrait (aspect<1): shrink y instead —
// dividing x by aspect<1 would WIDEN x and push text off-screen.
pos.x = pos.x / max(aspect, 1.0);
pos.y = pos.y * min(aspect, 1.0);
var out: VSOut;
out.clip = vec4f(pos, 0.0, 1.0);
out.uv = vec2f(mix(uv_min.x, uv_max.x, corner.x), mix(uv_max.y, uv_min.y, corner.y));
out.info = glyph.info;
out.color = glyph.color;
return out;
}
@fragment
fn fs_text(in: VSOut) -> @location(0) vec4f {
let atlas_px = vec2f(textureDimensions(atlas_tex, 0));
let msdf = textureSample(atlas_tex, atlas_sampler, in.uv).rgb;
let dist = median3(msdf);
let uv_width = max(fwidth(in.uv), vec2f(1.0 / max(atlas_px.x, 1.0), 1.0 / max(atlas_px.y, 1.0)));
let texels_per_px = max(length(uv_width * atlas_px), 0.0001);
let screen_range = max(0.5, 4.0 / texels_per_px);
let px_dist = screen_range * (dist - 0.5);
let coverage = clamp(px_dist + 0.5, 0.0, 1.0);
let reveal_span = max(1.0, in.info.y);
let reveal = clamp((params.frame - in.info.x) / reveal_span, 0.0, 1.0);
let alpha = coverage * in.color.a * reveal;
if (alpha < 0.001) { discard; }
let rgb = in.color.rgb * params.brightness;
return vec4f(rgb * alpha, alpha);
}
`;

View file

@ -0,0 +1,243 @@
import type { ObservatoryEngine, FramePass } from '$lib/observatory/engine';
import { rgb01 } from '$lib/observatory/cognitive-palette';
import { layoutText, type GlyphInstance } from './layout';
import { loadMsdfAtlas, type LoadedMsdfAtlas } from './msdf-atlas';
import { MSDF_TEXT_WGSL } from './shaders/msdf-text.wgsl';
type RoutePick = { id: string; kind: string; index?: number; payload?: unknown };
export type TextLayerItem = {
id?: string;
kind?: string;
text: string;
/** Logical NDC anchor, before shader's aspect divide. y is baseline, +Y up. */
x: number;
y: number;
/** NDC-Y units per em. */
size?: number;
color?: [number, number, number, number];
startFrame?: number;
revealSpan?: number;
maxWidthEm?: number;
maxLines?: number;
};
type TextRunRect = {
id: string;
kind: string;
text: string;
x0: number;
x1: number;
y0: number;
y1: number;
payload: TextLayerItem;
};
const GLYPH_FLOATS = 20;
const DEFAULT_COLOR: [number, number, number, number] = [...rgb01('#22C7DE'), 1];
export class TextLayerPass implements FramePass {
private engine: ObservatoryEngine;
private atlas: LoadedMsdfAtlas | null = null;
private bindLayout: GPUBindGroupLayout | null = null;
private pipeline: GPURenderPipeline | null = null;
private glyphBuffer: GPUBuffer | null = null;
private bindGroup: GPUBindGroup | null = null;
private glyphCapacity = 0;
private glyphCount = 0;
private pendingItems: TextLayerItem[] = [];
private runs: TextRunRect[] = [];
private initPromise: Promise<void> | null = null;
constructor(engine: ObservatoryEngine) {
this.engine = engine;
}
async init(): Promise<void> {
if (this.initPromise) return this.initPromise;
this.initPromise = this.initInner();
return this.initPromise;
}
private async initInner(): Promise<void> {
const device = this.engine.gpuDevice;
if (!device || !this.engine.paramsBuffer) return;
this.atlas = await loadMsdfAtlas(device);
this.ensurePipeline(device);
if (this.pendingItems.length) this.uploadItems(this.pendingItems);
}
setText(items: string | TextLayerItem | TextLayerItem[]): void {
const normalized = typeof items === 'string' ? [{ text: items, x: -0.62, y: 0, size: 0.075 }] : Array.isArray(items) ? items : [items];
this.pendingItems = normalized;
this.uploadItems(normalized);
}
private ensurePipeline(device: GPUDevice): void {
if (this.pipeline || !this.engine.paramsBuffer) return;
const module = device.createShaderModule({ label: 'msdf-text-wgsl', code: MSDF_TEXT_WGSL });
this.bindLayout = device.createBindGroupLayout({
label: 'msdf-text-bind-layout',
entries: [
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },
{ binding: 1, visibility: GPUShaderStage.VERTEX, buffer: { type: 'read-only-storage' } },
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }
]
});
const layout = device.createPipelineLayout({ label: 'msdf-text-pipeline-layout', bindGroupLayouts: [this.bindLayout] });
const overBlend: GPUBlendState = {
color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' },
alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha', operation: 'add' }
};
this.pipeline = device.createRenderPipeline({
label: 'msdf-text-pipeline',
layout,
vertex: { module, entryPoint: 'vs_text' },
fragment: { module, entryPoint: 'fs_text', targets: [{ format: this.engine.sceneFormat, blend: overBlend }] },
primitive: { topology: 'triangle-list' }
});
}
private uploadItems(items: TextLayerItem[]): void {
const device = this.engine.gpuDevice;
if (!device || !this.engine.paramsBuffer || !this.atlas) return;
this.ensurePipeline(device);
const packed: number[] = [];
const runs: TextRunRect[] = [];
let glyphIndex = 0;
items.forEach((item, itemIndex) => {
const size = item.size ?? 0.075;
const laid = layoutText(item.text, this.atlas!, {
maxWidthEm: item.maxWidthEm
});
const color = item.color ?? DEFAULT_COLOR;
const runGlyphs = laid;
let x0 = Number.POSITIVE_INFINITY;
let x1 = Number.NEGATIVE_INFINITY;
let y0 = Number.POSITIVE_INFINITY;
let y1 = Number.NEGATIVE_INFINITY;
for (const glyph of runGlyphs) {
const gx0 = item.x + glyph.x * size;
const gx1 = item.x + (glyph.x + glyph.w) * size;
const gy0 = item.y + glyph.y * size;
const gy1 = item.y + (glyph.y + glyph.h) * size;
x0 = Math.min(x0, gx0);
x1 = Math.max(x1, gx1);
y0 = Math.min(y0, gy0);
y1 = Math.max(y1, gy1);
packGlyph(packed, item, glyph, color, size, glyphIndex++);
}
if (runGlyphs.length > 0) {
runs.push({
id: item.id ?? `msdf-text:${itemIndex}`,
kind: item.kind ?? 'text',
text: item.text,
x0,
x1,
y0,
y1,
payload: item
});
}
});
this.runs = runs;
this.glyphCount = packed.length / GLYPH_FLOATS;
const floats = new Float32Array(packed.length || GLYPH_FLOATS);
floats.set(packed);
this.ensureGlyphBuffer(device, Math.max(1, this.glyphCount));
if (!this.glyphBuffer || !this.bindLayout) return;
device.queue.writeBuffer(this.glyphBuffer, 0, floats);
this.bindGroup = device.createBindGroup({
label: 'msdf-text-bind-group',
layout: this.bindLayout,
entries: [
{ binding: 0, resource: { buffer: this.engine.paramsBuffer } },
{ binding: 1, resource: { buffer: this.glyphBuffer } },
{ binding: 2, resource: this.atlas.sampler },
{ binding: 3, resource: this.atlas.textureView }
]
});
}
private ensureGlyphBuffer(device: GPUDevice, glyphCount: number): void {
if (this.glyphBuffer && this.glyphCapacity >= glyphCount) return;
this.glyphBuffer?.destroy();
this.glyphCapacity = Math.max(glyphCount, Math.ceil(this.glyphCapacity * 1.5), 32);
this.glyphBuffer = device.createBuffer({
label: 'msdf-text-glyphs',
size: this.glyphCapacity * GLYPH_FLOATS * 4,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
});
}
render(pass: GPURenderPassEncoder): void {
if (!this.pipeline || !this.bindGroup || this.glyphCount <= 0) return;
pass.setPipeline(this.pipeline);
pass.setBindGroup(0, this.bindGroup);
pass.draw(6, this.glyphCount);
}
pickAt(ndcX: number, ndcY: number): RoutePick | null {
const aspect = Math.max(0.0001, (this.engine.params[6] || 1) / Math.max(1, this.engine.params[7] || 1));
// Mirror the shader's square-in-both-orientations transform (msdf-text.wgsl):
// x /= max(aspect,1); y *= min(aspect,1).
const xScale = Math.max(aspect, 1);
const yScale = Math.min(aspect, 1);
for (const run of this.runs) {
const x0 = run.x0 / xScale;
const x1 = run.x1 / xScale;
const y0 = run.y0 * yScale;
const y1 = run.y1 * yScale;
if (ndcX >= x0 && ndcX <= x1 && ndcY >= y0 && ndcY <= y1) {
return { id: run.id, kind: run.kind, payload: run.payload };
}
}
return null;
}
dispose(): void {
this.glyphBuffer?.destroy();
this.glyphBuffer = null;
this.atlas?.dispose();
this.atlas = null;
this.bindGroup = null;
this.pipeline = null;
}
}
function packGlyph(
out: number[],
item: TextLayerItem,
glyph: GlyphInstance,
color: [number, number, number, number],
size: number,
glyphIndex: number
): void {
const ageFrame = (item.startFrame ?? 0) + glyphIndex * 2;
const revealSpan = item.revealSpan ?? 18;
out.push(
item.x,
item.y,
glyph.w * size,
glyph.h * size,
glyph.x * size,
glyph.y * size,
0,
0,
glyph.u,
glyph.v,
glyph.u + glyph.uw,
glyph.v + glyph.vh,
ageFrame,
revealSpan,
0,
0,
color[0],
color[1],
color[2],
color[3]
);
}

View file

@ -0,0 +1,50 @@
<script lang="ts">
import { onDestroy } from 'svelte';
import ObservatoryCanvas from '$lib/components/ObservatoryCanvas.svelte';
import type { ObservatoryEngine } from '$lib/observatory/engine';
import { TextLayerPass } from '$lib/observatory/text/text-layer';
let hostEl: HTMLDivElement | null = $state(null);
let textPass: TextLayerPass | null = null;
async function handleReady(engine: ObservatoryEngine) {
const pass = new TextLayerPass(engine);
textPass = pass;
await pass.init();
pass.setText({
id: 'msdf-test-line',
kind: 'msdf-test',
text: 'hello | 5de3e41f | trust 51%',
x: -0.62,
y: 0.03,
size: 0.105,
color: [0.14, 0.78, 0.87, 1],
startFrame: 0,
revealSpan: 20,
maxWidthEm: 18
});
engine.addPass(pass);
engine.demoClock.reset();
}
function handlePointerDown(e: PointerEvent) {
if (!hostEl || !textPass) return;
const rect = hostEl.getBoundingClientRect();
if (rect.width <= 0 || rect.height <= 0) return;
const ndcX = ((e.clientX - rect.left) / rect.width) * 2 - 1;
const ndcY = -(((e.clientY - rect.top) / rect.height) * 2 - 1);
const hit = textPass.pickAt(ndcX, ndcY);
if (hit) console.info('[msdf-test] picked', hit.id);
}
onDestroy(() => {
textPass?.dispose();
textPass = null;
});
</script>
<!-- Transparent pointer host only; ObservatoryCanvas owns the single visible canvas. -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div bind:this={hostEl} class="fixed inset-0 bg-[#020307]" onpointerdown={handlePointerDown}>
<ObservatoryCanvas demo="recall-path" seed="msdf-test-v1" onready={handleReady} />
</div>

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB