Skip to content

feat(registry): add the halftone-field texture block - #3112

Closed
miguel-heygen wants to merge 1 commit into
mainfrom
feat/primitive-dot-field
Closed

feat(registry): add the halftone-field texture block#3112
miguel-heygen wants to merge 1 commit into
mainfrom
feat/primitive-dot-field

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

halftone-field: a grid of dots whose size and colour track a flowing noise field, so broad diagonal ridges sweep across a mostly-black frame.

Why

The catalog has no texture register at all. This is a background that runs under type without competing with it, and it gives us a general "scalar field to halftone" pass to point at other content later.

How

Closed form by construction. The field is a pure function of position and time, so the entire port is uTime = frame / fps: no accumulator, no PRNG, no history anywhere.

One pass rather than the reference's two. Its first pass is analytic, so evaluating the noise at each cell centre returns exactly what a texture fetch would. The render target only earns its place if the field ever becomes something that cannot be evaluated closed-form (a real texture, a feedback buffer).

Repaint is driven from a property setter on the tweened driver rather than a timeline onUpdate callback, because GSAP suppresses events on seek by default and a canvas driven that way freezes on frame 0.

The two defaults that depart from the reference

Worth reading before assuming they are taste. The reference's own arithmetic contradicts its stated look.

At paletteBias = 10 the bias is 0.5. Since gray = luma^4.84 lands in [0, 0.70], the value driving both dot radius and palette position is pinned to [0.5, 1.0]. Two consequences:

  • every dot has a hard floor of 0.25 cells, so nothing can ever approach black
  • the palette window covers only stops 2 to 4, which makes #00AAFF arithmetically unreachable

Rendered, that is a flat evenly-lit purple-grey lattice: the same failure the gamma is supposed to prevent, arriving through bias instead.

Every formula and mapping is kept verbatim. Only two dial defaults move, paletteBias 10 -> 2 and frequency 5 -> 2, and both are one slider from the reference values.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

hyperframes check passes with 0 errors: layout 0 issues across 9 samples, motion 0, contrast 0. Registry item lint 0 errors. oxfmt clean. The runtime warnings are llvmpipe ReadPixels GPU-stall notices from software rendering.

WebGL surviving seek-capture was the main risk, so it was tested rather than assumed:

seek    -> t=3.0 (run 1): 3f8a92ccb2c4...
seek    -> t=3.0 (run 2): 3f8a92ccb2c4...
stepped -> t=3.0        : 3f8a92ccb2c4...

Direct seek equals stepping 90 frames equals a fresh run. Two CLI snapshot --at 4.2 runs are byte-identical. Frames were rendered and looked at at 0/2/4/6/8/9.9s: the main ridge travels lower-left to centre to upper-right, movement is already visible at 0.25s, and the troughs stay near-black with real gaps between dots.

The quantise toggle has its own check: stepped t=3.000 equals t=3.020 (same 30fps bucket) and differs from t=3.050, while smooth mode separates all three.

Not covered

Frame 0 is already lit rather than black. "Near-black to a blaze in under a second" describes the band sweep, not an intro, and a scripted fade would contradict the closed-form contract. An actual entrance is a small addition if wanted.

No catalog preview PNG; the mdx points at a URL that 404s until scripts/upload-docs-images.sh runs with the publishing profile. Maintainer step.

The file trips the 300-line advisory warning at 348 lines, about 90 of which are the simplex-noise function. Not splitting that.

A grid of dots whose size and colour track a flowing noise field, so broad
diagonal ridges sweep across a mostly-black frame. The catalog has no
texture register at all, and this is a background that runs under type
without competing with it.

Closed form by construction: the field is a pure function of position and
time, so the whole port is uTime = frame / fps. No accumulator, no PRNG,
no history. Direct seek, stepped playback and a repeat run all hash
identically.

One pass rather than the reference's two. Its first pass is analytic, so
evaluating the noise at each cell centre returns exactly what a texture
fetch would; the render target only earns its place if the field ever
becomes something that cannot be evaluated closed form.

Two defaults depart from the reference, and its own arithmetic is why. At
paletteBias 10 the bias is 0.5, and since the gamma curve lands in
[0, 0.70], the value driving both radius and colour is pinned to [0.5, 1].
That puts a hard floor under every dot so nothing can reach black, and
narrows the palette window so the first colour stop is unreachable. It
renders as a flat evenly-lit lattice. Every formula and mapping is kept
verbatim; only paletteBias and frequency move, and both are one slider
from the reference values.
@mintlify

mintlify Bot commented Aug 8, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Aug 8, 2026, 4:22 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Superseded by #3116, which carries all five video primitives in one PR as requested. Same commits, same verification; nothing dropped.

@miguel-heygen
miguel-heygen deleted the feat/primitive-dot-field branch August 9, 2026 15:43
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