Skip to content

feat(labels): MSDF text label foundations (Phases 1-2 + WGSL shader)#38

Merged
rulkens merged 11 commits intomainfrom
feat/msdf-labels-foundations
May 7, 2026
Merged

feat(labels): MSDF text label foundations (Phases 1-2 + WGSL shader)#38
rulkens merged 11 commits intomainfrom
feat/msdf-labels-foundations

Conversation

@rulkens
Copy link
Copy Markdown
Owner

@rulkens rulkens commented May 7, 2026

Summary

Lands Phases 1–2 (plus the labels WGSL shader from Phase 3) of the MSDF text label plan (docs/superpowers/plans/2026-05-07-msdf-labels.md). Stops short of the renderer code (Tasks 8–11) and engine integration (Phase 4) because both will conflict with the parallel engine + WGSL→WESL rewrite happening in another worktree.

What's in:

  • Phase 1 — atlas build pipeline
    • data/raw/fonts/JetBrainsMono-Regular.ttf (267 KB OFL-licensed, force-added past the /data/ ignore with an explicit allow rule).
    • tools/buildFontAtlas.ts + minimal .d.ts shim for msdf-bmfont-xml (no upstream types).
    • npm run build-font — deterministic across reruns (verified by md5).
    • Generated public/fonts/jetbrains-mono.png (1024² RGBA MSDF, 92 KB) + .json (100 glyphs, 33 KB).
  • Phase 2 — pure logic with TDD (12 new tests)
    • fontMetrics.ts — BMFont JSON → FontMetrics (atlas dims, distance range, glyph map, kerning map).
    • labelLayout.ts(text, metrics) → GlyphQuad[], monospace-friendly (kerning-aware), silently drops missing glyphs.
    • youAreHereVisibility.ts — distance → fade alpha (smoothstep between 0.6 and 2.0 Mpc).
  • Phase 3 partial — labels.wgsl shader only
    • Hybrid (clamped) screen-space sizing, MSDF median3 + fwidth smoothing, premultiplied output.
    • Not consumed yet (labelRenderer.ts is deferred), so this is dead code on main until the renderer lands.

What's NOT in (deferred):

  • labelRenderer.ts, markerLines.wgsl, markerLineRenderer.ts — Tasks 8–11. They'd ?raw-import shader files the parallel rewrite is converting WGSL→WESL, and would also need to wire into engine pieces being restructured. Re-targeting against the post-rewrite engine + WESL layout makes more sense than building twice.
  • Phase 4 (engine integration, you-are-here marker controller) — entirely engine-shaped, depends on the rewrite landing first.

Plan-vs-codebase notes

A few small drift items the plan didn't account for, surfaced and fixed inline:

  • /data/ is gitignored. The plan's font-asset path needed an explicit allow rule + initial force-add; .gitignore won't re-include files whose ancestor is excluded.
  • Project pins exact dependency versions, so msdf-bmfont-xml@^2.7.0 from the plan landed as 2.8.0 (current).
  • noUncheckedIndexedAccess: true in tsconfig.json — the plan's test fixtures and renderer state code needed ! non-null assertions on array accesses.
  • skipLibCheck: true masked a TS1038 in the .d.ts shim (declare inside declare module); fixed so the IDE diagnostic is clean too.

Test plan

  • npm run typecheck — both tsconfig.json and tsconfig.tools.json pass.
  • npm test — 908 passing (was 895 on main); 12 new tests across the 3 pure-logic modules.
  • npm run build-font — emits expected sizes; deterministic across reruns.
  • (manual) Eyeball public/fonts/jetbrains-mono.png — should look like a dark image with multicoloured glyph shapes (RGB-channel SDF encoding).

Notes for follow-up branch

When the engine rewrite + WESL conversion lands, the deferred work becomes:

  1. Convert labels.wgsl to whatever WESL layout the rewrite settled on.
  2. Re-derive Tasks 8, 9, 11 against the new state.gpu.* / renderFrame.ts shape (per the survey on top of the existing plan).
  3. Pick up Task 10 (markerLines.wgsl) directly in WESL.
  4. Resume Phase 4 wiring against the post-rewrite engine API.

🤖 Generated with Claude Code

rulkens and others added 10 commits May 7, 2026 23:25
… ignore)

Previous commit f4011e8 added the .gitignore exception but git's 'parent
directory excluded' rule means files under /data/raw/ can't be re-included
via .gitignore alone — the rule needs a force-add for the initial track,
after which the explicit !/data/raw/fonts/JetBrainsMono-Regular.ttf line
keeps it tracked across future status checks.
Co-Authored-By: Claude <noreply@anthropic.com>
skipLibCheck:true masks this from tsc but the IDE flags it as TS1038
('declare modifier cannot be used in an already ambient context').
Inside a 'declare module' block, function declarations are already
ambient — the inner 'declare' is noise.
Co-Authored-By: Claude <noreply@anthropic.com>
Implements layoutLabel() which converts a text string into GlyphQuad
attribute tuples using BMFont metrics, with kerning and silent drop of
missing glyphs.  Five unit tests cover count, sequential positioning,
kerning, total width, and UV emission.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 7, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
skymap b6bdbd7 Commit Preview URL

Branch Preview URL
May 07 2026, 10:07 PM

The 1024² atlas was ~93% empty: 100 glyphs (95 ASCII + 5 unit symbols)
at fontSize 42 only need a ~213×512 bounding box.  Dropping to 512²
keeps a comfortable packing margin while quartering the rgba16float
upload (4 MB → 1 MB).  PNG-on-disk savings are modest (91.5 → 84.6 KB)
because the original empty area compressed well, but GPU memory is the
real win.  256² would require ≥12 rows of ~48 px and doesn't fit.
@rulkens rulkens merged commit d09cd5a into main May 7, 2026
2 checks passed
rulkens added a commit that referenced this pull request May 7, 2026
….wesl

Brings the MSDF labels foundation (added on main in #38) under the
WESL convention: directory layout, three-file split, lib/camera
adoption.

The Uniforms struct now embeds 'cam: CameraUniforms' (80-byte universal
prefix) instead of declaring its own viewProj+viewport pair — both
layouts are byte-equivalent at offsets 0..79, so no CPU-side change is
required when the labels renderer eventually wires up a uniform writer.

No consumer existed yet (foundation-only PR), so no renderer.ts to
update.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant