Repository navigation
V3: Annotations — regions placement, connected dots, rotation - #196
Merged
git-chad merged 143 commits intoOct 1, 2026
Merged
Conversation
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
…roup failures Media load callbacks now ignore replaced or removed passes and pending loads are cleared on disposal. Strict-mode pass failures are reported once at the originating pass instead of at every group boundary.
Publishing now drops every descendant of a hidden group and clears the parentId of any retained layer (the audio source) whose parent was removed, so the published scene passes hierarchy validation on reopen.
Cap the neighbor row and column search to two cells each side so the worst-case cellAt count per fragment is 25 instead of unbounded in the packed minimum-size, maximum-stroke configuration. Mirrored in the runtime package.
…e on buttons Clear the active paint layer when the layer stops being paintable (selection, mode, visibility or lock change) so Paint never resumes without Edit Paint. Only preventDefault on Space for non-interactive targets so focused buttons and links still activate.
three 0.183.2 -> 0.185.1 and @types/three 0.185.4. The published runtime now asks for three ^0.185.0, so projects on 0.183 have to update three with it (changeset included). @webgpu/types is now an explicit dev dependency referenced from src/types/webgpu.d.ts, because @types/three stops pulling it in with 0.186. 0.186.1, the latest release, was tried first and set aside. It has a TSL codegen bug (three #34339, still open): a shared math value first read inside an If/select branch is cached in a variable assigned only in that branch, so every other read sees 0. It broke the Photographic Cells outline at gap 0 and Focus Blur, and a scan of every WGSL shader the composition suite generates found the same uninitialized reads in Glass, Connected Dots, Photocopy and Plotter, with none on 0.185.1. r186 started counting usage through ContextNode, so far more nodes get cached than before. Turning that cache off inside branches and loops fixed Cells but made glass and blur shaders compile several times slower and still failed a Focus Blur check, so it is not worth shipping. r187 rewrites this cache; retry then with the full suite and the WGSL scan. The 3D layer no longer skips compileAsync for transmissive models: 0.183 still throws the transmission CopyTextureToTexture format error without the skip, 0.185 does not. The exact-pose uniform refresh stays, since three only fixes that in r186 (0.185 without it fails the pose-return test). The shadow node-frame advance and the persistent environment node stay as well. Visible changes: environment rotation follows three r184 (same direction as rotating an object), so Studio Rotation turns the other way than on 0.183; the layer is unreleased, so there is no migration. The r185 premultiplied- alpha output change only touches transparent output pixels and the canvas is opaque; all alpha checks pass. three now reports uncaptured GPU errors through console.error. SwiftShader cannot import decoded video frames, which produces the same 64 validation errors on 0.183 (Chrome printed them as warnings, so they were never counted). The composition suite now ignores exactly that ExternalTexture chain, prints how many it skipped, and still fails on any other GPU error. The roadmap records the new order the user asked for on September 29: three upgrade, animation clips, SVG to 3D, and the path-traced Render mode last.
The shader reference lists every three/tsl export so the MCP can tell agents which TSL functions exist. three 0.185 adds packNormalToRGB, unpackRGBToNormal, transformNormalByInverseViewMatrix and the other names from the r184 and r185 releases, so CI's snapshot check failed until the generated file was refreshed.
Step 3 of roadmap 4.4 (4.3 in Fase 4). The Model section lists the clips of a .glb, read straight from the glTF JSON (names and durations from the animation input accessors), so the list shows without loading the model and works for projects saved before this change. Clip picks one clip, All clips plays every clip together, None shows the rest pose. The automatic default plays every clip when each clip moves different objects (per-object actions from Blender) and otherwise the first one, so a character with Idle/Walk/Run on one skeleton does not blend them. Play, Speed (0-4x), Repeat (Loop, Ping-Pong, Once) and Start map composition time to clip time: Start + time * Speed, wrapped by the repeat mode, set on each action and evaluated with mixer.update(0). That keeps preview, PNG and video export identical frame by frame; paused shows the Start frame. The clip settings are hidden registry params rendered in the Model section, so they save, undo and duplicate like any other param. Importing an animated model seeds the timeline length from the playing clips, like video. Framing, floor height and shadows are computed over 24 poses spread across the selected clips, so a moving model stays in frame and on its floor. The depth pass, shadows and replacement materials follow skinned and morphed meshes; Brushed Metal and Iridescent noise uses positionGeometry so it stays on the surface instead of sliding while bones move. Floor sampling now evaluates only the vertices it keeps. model-animation.mjs adds 44 GPU checks: clip timing math, default clips, parsing, poses over time, clip-wide framing, animated depth, speed, pause, repeat modes, Start, None, All clips, skinning, timeline seeding, save and reopen, and exported frames. The editor UI test imports an animated .glb and checks the clip list, the timeline length and the Play toggle.
- Fit animated bounds from keyframe-aware pose samples with a larger per-pose vertex limit - Give each clip a unique selection value so duplicate or reserved names resolve correctly - Read the input accessor data when its max is missing so clip duration is not zero - Seed the timeline duration when replacing a model - Stop requesting renders once a Once clip has finished
Step 4 of roadmap 4.4 (Fase 5). Add layer -> 3D Model now also accepts .svg: the asset is stored as a model with its image/svg+xml type, and the layer is a normal 3D Model layer, so framing, Blender-style transforms, materials, studios, shadows and exact depth all carry over. Dropping an SVG on the canvas still makes an image layer. Replace accepts .glb, .gltf and .svg. Supported: paths with every command, rect, circle, ellipse, polygon, polyline, line, groups with transforms, fill colors and fill-rule (nonzero and evenodd, so holes and compound paths stay open), and strokes with their width, joins and caps. Text has to be converted to outlines first; an SVG with only text shows that message. Images, filters, masks, clip paths and patterns are ignored, gradients fall back to a solid color. Fills go through ShapePath.toShapes() (r185 replaces SVGLoader.createShapes), are flattened at 48 points per curve with SVG's y-down axis flipped at the shape level, and extruded with ExtrudeGeometry. Bevel uses a negative bevel offset, so it rounds the front and back without growing the silhouette. Strokes come from SVGLoader.pointsToStroke and are extruded as straight prisms from their triangles, with side walls on boundary edges. Depth and bevel are shares of the logo's larger side. Each later path sits slightly in front of the one before so stacked shapes do not z-fight, and crease-angle normals keep curved walls smooth and corners sharp (chrome showed facets on the inner wall of a ring without them). Original uses the SVG fill colors on a satin standard material. Changing Depth, Bevel or Bevel Smoothness rebuilds the geometry from the parsed SVG, and the SVG builder loads on demand. The default bevel is 0.02 of the logo size: renders on the flat yellow of reference 31 showed chrome only catching light on beveled edges. model-svg.mjs adds 21 checks: the ring's hole, bevel inside the outline, depth as a share of size, y-up, fill colors, stacked paths, stroke-only shapes, the text-only message, the picker accepting .svg, extrusion depth seen side-on, replacement materials, depth output and save/reopen. The editor UI test imports an SVG and checks the Extrude controls and the saved asset kind.
three 0.186.1 compiles some TSL graphs with a variable assigned inside only one branch of an If/select and read outside it, so other pixels read 0 (three #34339). None of the composition checks fail on most of those reads, so upgrades need a second gate. wgsl-capture.mjs runs every registered composition check (discovered from window, plus the baseline projects) and saves each unique shader module the checks create. THREE_DIR swaps in an unpacked three package through a Bun build plugin, so a candidate version can be captured without touching the installed one; CHECKS limits the run. wgsl-scan.mjs tracks which variables each block has assigned, treating an if/else chain as assigning a variable only when every arm does, and reports reads with no assignment on the path. Given a baseline capture it reports only new patterns and exits non-zero. On the September 29 captures it finds 40 new patterns on 0.186.1 in exactly the six layers that broke, and none comparing 0.185.1 with itself. The roadmap gets a "Para retomar" block: what is pending acceptance, the unfinished full run on the stack and unconfirmed CI, the six steps for moving to r187 (types, runtime peer and changeset, MCP reference, dropping the r186-fixed pose workaround, suite, shader scan), and that the path-traced Render mode can start on 0.185.1.
Connected Dots cost 43-50 ms per frame at 1080p on an Apple GPU and was the main source of heat. Each pixel recomputed 81 dot sites; a pre-pass now stores the 25 distinct sites once per cell and the shader reads them back, and only the active mode's link maths runs. It now takes about 5 ms. Other passes, native GPU at 1080p, before -> after (ms): Displaced Rings 5.4 -> 2.3, Focus Blur depth of field 3.2 -> 0.3 and tilt-shift 3.4 -> 1.9, Glass hammered 3.5 -> 1.3, Plotter stipple 3.2 -> 1.0, Signal Rot 1.95 -> 0.65, Outline stroke 2.0 -> 1.2, Photocopy 1.0 -> 0.4-0.85. The savings come from running only the selected pattern, mode or shape, skipping work whose slider is 0, sharing dependency chains with Fn/toVar, and skipping the blur pyramid when no pixel reaches its first level. The editor pipeline redrew every layer each frame whenever any layer animated. It now caches the static layers at the bottom of the stack when a continuous layer sits above them, and reuses them until something changes. A photo with Connected Dots and Glass under an animated Signal Rot goes from 8.0 to 0.9 ms per frame, and the cached frame is pixel-identical to a full render. Passes now learn whether their input may have changed this frame. Blob Tracking uses it to stop forcing continuous rendering once its temporal state settles (it previously redrew the whole pipeline forever), restarts when a layer below changes, and joins the static cache while settled. Annotations re-read the Edges field only when their input changes instead of every third frame, and shade only each mark's own kind. Outline, Flares and Relief bound two texture nodes to one placeholder, which merged into a single binding when the material compiled before its first render; they now use separate placeholders. Verified with the composition suite (all checks pass; 83 of 95 renders byte-identical, the rest within 1/255 except 4 pixels at 3/255) and a native-GPU dump of every style of every V3 layer at 1080p against HEAD. Lumen Print Sabattier re-rolls grain on single-pixel columns and Dot Grid Night moves a few dot edges by less than a pixel at rounding ties.
… asset in the reopen test - Carry fill-opacity and stroke-opacity onto the SVG materials so semi-transparent logo parts stay translucent in Original mode - Make the save/reopen check register the SVG asset, assert it resolves after reopen, and render the reopened project
Preset -> Custom shows a Motifs tray under the preset: up to ten images or SVGs, added several at a time with the + tile or by dropping files on the tray. Dragging a tile lifts and tilts it while the others make room (the user's sketch, reference 35); the drop is one undo step and Esc cancels. Alt+arrows reorder and Delete removes from the keyboard. Motifs are drawn into an RGBA atlas that keeps their colors, instead of the presets' thresholded mask. The first motif covers the lightest perceptual tones and the last the darkest, in equal bands; Invert reverses the order. The new Motif colors mode draws each motif's own colors over the Background, and is chosen when the first motifs are added if the mode was still Source. The other modes tint the motif's silhouette through its alpha. Bars, Candles and Shapes are unchanged. The ordered list is `patternAssetIds` on the layer, so it follows save, autosave, history, duplication and publishing through collectReferencedAssetIds. A motif missing on reopen is dropped with "Missing motif: <file>"; one that fails to decode is reported while the others still render. The runtime config gains `patternAssets`, with a distinct /replace/image/ placeholder per motif. The limit of ten keeps a scene under the community's twelve-asset cap. pattern-motifs.mjs checks the editor and runtime passes on a tone ramp, the project path and runtime parity; reversing the bands fails it.
… paths, drag handling - Keep the motif load warning until a load succeeds (src and runtime pass) - Treat Motif colors as source color for built-in pattern presets - Make exported motif placeholder paths unique across all layers - End the undo interaction when a tile loses pointer capture or is cancelled, outside the state updater - Check motif capacity after loading and report files that did not fit
Shape -> SVG takes an uploaded file as the layer's asset. The SVG is rasterized once at 1024 px on its long side with a margin, and an exact Euclidean distance transform (inside and outside, refined by the anti-aliased alpha at the edge) turns it into a half-float signed distance field. The shape shader samples it as kind 7 of the same branchless shader, so switching shapes still doesn't recompile, edges stay sharp at any size, and Softness and Outline mean exactly what they mean on the procedural shapes. A distance field was chosen over rasterizing like the Image layer for that reason. Original colors come from a color texture whose colors are dilated outward, so softness never shows a dark fringe. File colors are listed from attributes, inline styles, <style> rules, gradient stops and the implicit black of shapes without a fill, and each can be changed; recoloring reuses the distance field and only redraws colors. Single color and Outline use the layer Color. Because the SVG is the layer's assetId, saving, autosave, history, duplication, publishing, the Missing asset notice and export work as on Image layers. Replaced fields are disposed a few frames later so a texture is never freed while still bound.
…opic edges - Keep SVG interior opacity through the distance field and color texture - Preserve alpha when extracting and replacing SVG file colors - Skip the implicit-fill swatch when a parent group sets fill - Retry a failed SVG load on the next sync - Keep the export wait entry until the current SVG request finishes - Scale sampled SVG distance per axis for stretched shapes
The ramp was a JSON text param, so it could not be animated. It is now one timeline property, Ramp, with a new "gradient" value type: the diamond next to "Dark to light" keys the whole ramp, Auto-Key edits at the playhead create or update keyframes, and without Auto-Key an edit only previews until keyed, like every other animated param. Between two keyframes the ramps are aligned stop by stop. With equal counts the sorted stops pair up and glide in position and color. With different counts the shorter ramp gains stops at the positions of the unmatched ones (a monotone matching that minimises position travel), colored by sampling the shorter ramp itself, so it looks unchanged and nothing pops at either keyframe; nested presets crossfade exactly. Colors blend in sRGB like the ramp and color tracks already do, per-keyframe easing applies, and the alignment is cached per keyframe pair. The same code runs in the editor and the runtime timeline, so preview, PNG/video export and shader export agree. The pass rewrites its LUT in place and only when the evaluated ramp string changes, so static scenes and projects without ramp tracks behave as before.
- Interpolate inserted ramp stops between the adjacent matched stops so overlapping stops keep the correct side of the color change (app and runtime copy) - Pass onInteractionStart/End through GradientRamp to its ColorPicker so a color drag is one interaction
The roadmap left three Annotations items open: connected dots (reference 21), placement guided by image regions, and rotating individual elements. Placement → Regions reuses the 128×72 field Edges already renders and reads back only when the image changes. A second edge material writes the Sobel magnitude unchanged plus averaged tone and two chroma axes; the CPU groups cells into regions with warm-started k-means, a majority filter and connected components, drops thin slivers, and keeps region ids across frames by overlap, so marks glide with video instead of blinking. Boxes frame compact regions, dots, crosses and labels sit on outlines picked by seeded anchors, rings sit on centers, and every per-region mark uses its own seeded stream so regions appearing or disappearing do not reshuffle the others. Connected Dots draws small constellations: a minimum spanning tree over a few seeded nodes plus occasional second-neighbour links, respecting every placement and drawn last so the element budget drops it first. Rotation Jitter turns rings, crosses and boxes by a hashed angle, and Align to Edges turns them along edge tangents or each region's long axis; text, rulers and metadata stay upright. Every new control starts off and draws from separate random streams, so saved projects keep their exact layout; a golden digest of legacy layouts computed from the previous code guards that. Plain Edges keeps its original material and field.
…sts in the harness The Annotations region study seeks through aura.mp4 72 times. Chrome aborts in-flight media range requests on every seek, which the harness counted as a failed request. Only media requests aborted by the element itself are ignored now; every other failed request still fails the run.
… edge angles, spread sparse marks - Annotations pass redraws its output once the edge/region readback completes so still texture sources no longer keep seeded marks - Edge tangents are converted from the 128x72 grid to the composition aspect before rotating marks - eachRegion spreads limited marks evenly across regions instead of filling the first IDs - Mirrored into packages/shader-lab-react
git-chad
force-pushed
the
git-chad/v3-gradient-map-keyframes
branch
from
October 1, 2026 14:03
6f2f49a to
8cf7b05
Compare
git-chad
force-pushed
the
git-chad/v3-annotations-more
branch
from
October 1, 2026 14:03
d4f6ed4 to
440f202
Compare
git-chad
changed the base branch from
git-chad/v3-gradient-map-keyframes
to
git-chad/shader-lab-v3-plan
October 1, 2026 15:15
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap 6.6 item 5: three additions to Annotations. It's stacked on #195 (Gradient Map keyframes). Everything is off by default: saved projects keep their exact layout, which a digest of 19 layouts from the previous code confirms.
What you get
Placement → Regions. It splits the image below into up to 16 regions by tone and colour and places marks by region:
WORD NNlabel;How it works: it reuses the 128×72 field that Edges already reads, only when the image changes, with no extra readbacks. Segmentation runs on the CPU in about 3 ms per update. Region ids carry over between frames, so marks follow video instead of flickering (0.13 new ids per frame on
aura.mp4).Elements → Connected Dots. Small constellations of 6–10 linked dots, each with a ringed hub and a code. They respect every placement mode, trace outlines in Regions, and are drawn last within the 320-mark budget.
Rotation Jitter turns rings, crosses and boxes by a seeded angle. Align to Edges, available with Edges or Regions, turns boxes and crosses along image edges or a region's long axis. Text, rulers and metadata stay upright.
Visibility rules. Parameter visibility gains a
oneOfcondition, used for "Edges or Regions".Tests
annotation-extras.mjs, 84 cases:annotations.mjsstill passes.bun run test:compositionsuite passes on this commit (SwiftShader).aura.mp4, and Chrome aborts media range requests on each seek.run.mjsnow ignoresnet::ERR_ABORTEDonly formediarequests; every other failed request still fails the run.Manual test:
tests/composition/ANNOTATION-EXTRAS-MANUAL-QA.md.Notes
🤖 Generated with Claude Code