@vibecook/ice 0.10.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -50
- package/dist/chunk-BA7XJC27.js +2429 -0
- package/dist/chunk-BA7XJC27.js.map +1 -0
- package/dist/chunk-BKV4ZEU3.js +1 -0
- package/dist/chunk-BKV4ZEU3.js.map +1 -0
- package/dist/{chunk-5FQS7V2C.js → chunk-DYK6KNLG.js} +13303 -7485
- package/dist/chunk-DYK6KNLG.js.map +1 -0
- package/dist/chunk-ENIFIUEP.js +155 -0
- package/dist/chunk-ENIFIUEP.js.map +1 -0
- package/dist/chunk-IBWWLA6D.js +634 -0
- package/dist/chunk-IBWWLA6D.js.map +1 -0
- package/dist/{chunk-OR5FNVUE.js → chunk-JS6PG6YD.js} +398 -167
- package/dist/chunk-JS6PG6YD.js.map +1 -0
- package/dist/chunk-QXOCF56A.js +148 -0
- package/dist/chunk-QXOCF56A.js.map +1 -0
- package/dist/chunk-ZPUPBULB.js +7399 -0
- package/dist/chunk-ZPUPBULB.js.map +1 -0
- package/dist/desk-engine.js +36 -0
- package/dist/desk-engine.js.map +1 -0
- package/dist/desk-kit.js +244 -0
- package/dist/desk-kit.js.map +1 -0
- package/dist/desk-objects.js +12728 -0
- package/dist/desk-objects.js.map +1 -0
- package/dist/desk.js +469 -0
- package/dist/desk.js.map +1 -0
- package/dist/devtools.js +233 -189
- package/dist/devtools.js.map +1 -1
- package/dist/dom.js +7 -19
- package/dist/index.js +232 -20
- package/dist/kernel.js +53 -25
- package/dist/react.js +1045 -38
- package/dist/react.js.map +1 -1
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +11 -0
- package/dist/types/packages/core/src/behavior/runtime.d.ts +4 -0
- package/dist/types/packages/core/src/canvas/define-canvas-type.d.ts +86 -0
- package/dist/types/packages/core/src/canvas/define-container.d.ts +17 -0
- package/dist/types/packages/core/src/canvas/diagnostics.d.ts +27 -0
- package/dist/types/packages/core/src/canvas/engine-catalog.d.ts +70 -0
- package/dist/types/packages/core/src/canvas/extensions.d.ts +125 -0
- package/dist/types/packages/core/src/canvas/frame-behavior-host.d.ts +16 -0
- package/dist/types/packages/core/src/canvas/frame-preview.d.ts +79 -0
- package/dist/types/packages/core/src/canvas/frame-projection.d.ts +38 -0
- package/dist/types/packages/core/src/canvas/frame-view.d.ts +36 -0
- package/dist/types/packages/core/src/canvas/migrate.d.ts +14 -0
- package/dist/types/packages/core/src/canvas/placement.d.ts +30 -0
- package/dist/types/packages/core/src/canvas/presentation-transition.d.ts +100 -0
- package/dist/types/packages/core/src/canvas/runtime-extension-host.d.ts +16 -0
- package/dist/types/packages/core/src/canvas/session.d.ts +47 -0
- package/dist/types/packages/core/src/catalog/camera-derived.d.ts +16 -27
- package/dist/types/packages/core/src/catalog/desk.d.ts +326 -0
- package/dist/types/packages/core/src/catalog/gesture.d.ts +51 -0
- package/dist/types/packages/core/src/catalog/graph.d.ts +2 -2
- package/dist/types/packages/core/src/catalog/index.d.ts +2 -0
- package/dist/types/packages/core/src/catalog/pointer.d.ts +42 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +7 -0
- package/dist/types/packages/core/src/catalog/settings-resources.d.ts +28 -2
- package/dist/types/packages/core/src/catalog/surface.d.ts +1 -0
- package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +18 -2
- package/dist/types/packages/core/src/doc/doc-kit.d.ts +21 -2
- package/dist/types/packages/core/src/doc/envelope.d.ts +5 -2
- package/dist/types/packages/core/src/doc/schema-migrate.d.ts +1 -1
- package/dist/types/packages/core/src/doc/version-gate.d.ts +21 -3
- package/dist/types/packages/core/src/engine/commit-sink.d.ts +10 -1
- package/dist/types/packages/core/src/engine/engine.d.ts +6 -0
- package/dist/types/packages/core/src/engine/frame-control.d.ts +74 -0
- package/dist/types/packages/core/src/engine/guests.d.ts +4 -1
- package/dist/types/packages/core/src/engine/world-doors.d.ts +12 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +146 -13
- package/dist/types/packages/core/src/index.d.ts +34 -13
- package/dist/types/packages/core/src/input/queue.d.ts +3 -1
- package/dist/types/packages/core/src/interaction/install.d.ts +30 -0
- package/dist/types/packages/core/src/interaction/wakes.d.ts +24 -0
- package/dist/types/packages/core/src/nav/nav-geometry.d.ts +100 -0
- package/dist/types/packages/core/src/nav/nested-canvas.d.ts +47 -4
- package/dist/types/packages/core/src/ops/cascade.d.ts +2 -1
- package/dist/types/packages/core/src/ops/claims.d.ts +6 -0
- package/dist/types/packages/core/src/ops/insert.d.ts +4 -0
- package/dist/types/packages/core/src/ops/point-pick.d.ts +4 -1
- package/dist/types/packages/core/src/ops/sibling-order.d.ts +14 -4
- package/dist/types/packages/core/src/ops/tray.d.ts +48 -0
- package/dist/types/packages/core/src/presence/presence-kit.d.ts +5 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +10 -0
- package/dist/types/packages/core/src/schema/prefab.d.ts +8 -0
- package/dist/types/packages/core/src/settings/defaults.d.ts +25 -6
- package/dist/types/packages/core/src/surface/gpu-device.d.ts +55 -0
- package/dist/types/packages/core/src/systems/camera-sim.d.ts +29 -0
- package/dist/types/packages/core/src/systems/chrome.d.ts +15 -23
- package/dist/types/packages/core/src/systems/drag-mods.d.ts +34 -0
- package/dist/types/packages/core/src/systems/held.d.ts +71 -0
- package/dist/types/packages/core/src/systems/insert-ghost.d.ts +2 -0
- package/dist/types/packages/core/src/systems/l0-input.d.ts +14 -0
- package/dist/types/packages/core/src/systems/l1-pick.d.ts +37 -1
- package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +18 -4
- package/dist/types/packages/core/src/systems/l3-behave.d.ts +4 -1
- package/dist/types/packages/core/src/systems/l3-claim.d.ts +7 -0
- package/dist/types/packages/core/src/systems/l3-connect.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l3-drop.d.ts +30 -1
- package/dist/types/packages/core/src/systems/l3-marquee.d.ts +2 -1
- package/dist/types/packages/core/src/systems/l3-ports.d.ts +1 -1
- package/dist/types/packages/core/src/systems/l4-cursor.d.ts +5 -0
- package/dist/types/packages/core/src/systems/nav-flight.d.ts +85 -3
- package/dist/types/packages/core/src/systems/nav-tap.d.ts +44 -0
- package/dist/types/packages/core/src/systems/press-wheel.d.ts +11 -0
- package/dist/types/packages/core/src/systems/tray.d.ts +108 -0
- package/dist/types/packages/core/src/systems/zoom-through.d.ts +30 -0
- package/dist/types/packages/core/src/tools/define-tool.d.ts +10 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +166 -63
- package/dist/types/packages/core/src/widget/equip.d.ts +14 -2
- package/dist/types/packages/core/src/widget/held-tools.d.ts +83 -0
- package/dist/types/packages/core/src/widget/menu-actions.d.ts +44 -0
- package/dist/types/packages/core/src/widget/mount-store.d.ts +21 -46
- package/dist/types/packages/core/src/widget/spawn.d.ts +1 -1
- package/dist/types/packages/desk/src/assets/blue-noise.gen.d.ts +10 -0
- package/dist/types/packages/desk/src/card/card.d.ts +103 -0
- package/dist/types/packages/desk/src/compose/ambient.d.ts +59 -0
- package/dist/types/packages/desk/src/compose/builder.d.ts +285 -0
- package/dist/types/packages/desk/src/compose/children.d.ts +3 -0
- package/dist/types/packages/desk/src/compose/index.d.ts +14 -0
- package/dist/types/packages/desk/src/compose/marks.d.ts +126 -0
- package/dist/types/packages/desk/src/compose/pick.d.ts +5 -0
- package/dist/types/packages/desk/src/compose/reflector.d.ts +111 -0
- package/dist/types/packages/desk/src/compose/tap.d.ts +5 -0
- package/dist/types/packages/desk/src/docs.d.ts +23 -0
- package/dist/types/packages/desk/src/engine/budget.d.ts +38 -0
- package/dist/types/packages/desk/src/engine/device.d.ts +54 -0
- package/dist/types/packages/desk/src/engine/index.d.ts +5 -0
- package/dist/types/packages/desk/src/engine/pipeline.d.ts +36 -0
- package/dist/types/packages/desk/src/engine/rasters.d.ts +66 -0
- package/dist/types/packages/desk/src/engine/records.d.ts +56 -0
- package/dist/types/packages/desk/src/engine/shader.d.ts +22 -0
- package/dist/types/packages/desk/src/engine/struct.d.ts +39 -0
- package/dist/types/packages/desk/src/engine/target.d.ts +26 -0
- package/dist/types/packages/desk/src/gpu-ablation.d.ts +61 -0
- package/dist/types/packages/desk/src/gpu-memory.d.ts +54 -0
- package/dist/types/packages/desk/src/gpu-profiler.d.ts +141 -0
- package/dist/types/packages/desk/src/gpu-wrap.d.ts +8 -0
- package/dist/types/packages/desk/src/ground.d.ts +388 -0
- package/dist/types/packages/desk/src/hold/focus.d.ts +53 -0
- package/dist/types/packages/desk/src/hold/pose.d.ts +132 -0
- package/dist/types/packages/desk/src/hold/shaders.d.ts +9 -0
- package/dist/types/packages/desk/src/host/editor.d.ts +18 -0
- package/dist/types/packages/desk/src/host/index.d.ts +9 -0
- package/dist/types/packages/desk/src/host/ink.d.ts +18 -0
- package/dist/types/packages/desk/src/host/layer.d.ts +430 -0
- package/dist/types/packages/desk/src/host/picture.d.ts +2 -0
- package/dist/types/packages/desk/src/host/surface.d.ts +5 -0
- package/dist/types/packages/desk/src/index.d.ts +13 -0
- package/dist/types/packages/desk/src/kind.d.ts +187 -0
- package/dist/types/packages/desk/src/kinds/world.d.ts +429 -0
- package/dist/types/packages/desk/src/kit/arrays.d.ts +88 -0
- package/dist/types/packages/desk/src/kit/blobs.d.ts +40 -0
- package/dist/types/packages/desk/src/kit/book.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/editor.d.ts +103 -0
- package/dist/types/packages/desk/src/kit/eye.d.ts +37 -0
- package/dist/types/packages/desk/src/kit/hold.d.ts +1 -0
- package/dist/types/packages/desk/src/kit/index.d.ts +26 -0
- package/dist/types/packages/desk/src/kit/inside.d.ts +116 -0
- package/dist/types/packages/desk/src/kit/layer.d.ts +159 -0
- package/dist/types/packages/desk/src/kit/light.d.ts +3 -0
- package/dist/types/packages/desk/src/kit/mesh.d.ts +29 -0
- package/dist/types/packages/desk/src/kit/mips.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/nav.d.ts +2 -0
- package/dist/types/packages/desk/src/kit/paper-tex.d.ts +5 -0
- package/dist/types/packages/desk/src/kit/physics.d.ts +44 -0
- package/dist/types/packages/desk/src/kit/place.d.ts +44 -0
- package/dist/types/packages/desk/src/kit/print.d.ts +30 -0
- package/dist/types/packages/desk/src/kit/raster.d.ts +34 -0
- package/dist/types/packages/desk/src/kit/sdf.d.ts +1 -0
- package/dist/types/packages/desk/src/kit/seeds.d.ts +11 -0
- package/dist/types/packages/desk/src/kit/services.d.ts +34 -0
- package/dist/types/packages/desk/src/kit/springs.d.ts +12 -0
- package/dist/types/packages/desk/src/kit/strokes.d.ts +93 -0
- package/dist/types/packages/desk/src/kit/text.d.ts +142 -0
- package/dist/types/packages/desk/src/kit/uniform.d.ts +8 -0
- package/dist/types/packages/desk/src/kit/view.d.ts +39 -0
- package/dist/types/packages/desk/src/kit/wgsl.d.ts +15 -0
- package/dist/types/packages/desk/src/lattice/line.d.ts +15 -0
- package/dist/types/packages/desk/src/lattice/lod.d.ts +52 -0
- package/dist/types/packages/desk/src/lattice/ruler.d.ts +88 -0
- package/dist/types/packages/desk/src/marks/assemble.d.ts +65 -0
- package/dist/types/packages/desk/src/marks/ease.d.ts +4 -0
- package/dist/types/packages/desk/src/marks/laser.d.ts +41 -0
- package/dist/types/packages/desk/src/marks/layout.d.ts +167 -0
- package/dist/types/packages/desk/src/marks/pass.d.ts +59 -0
- package/dist/types/packages/desk/src/marks/shaders.d.ts +12 -0
- package/dist/types/packages/desk/src/mat/grid.d.ts +50 -0
- package/dist/types/packages/desk/src/mat/lamp.d.ts +14 -0
- package/dist/types/packages/desk/src/mat/layout.d.ts +144 -0
- package/dist/types/packages/desk/src/mat/mat-pass.d.ts +83 -0
- package/dist/types/packages/desk/src/mat/night.d.ts +95 -0
- package/dist/types/packages/desk/src/mat/projector.d.ts +39 -0
- package/dist/types/packages/desk/src/mat/shaders.d.ts +17 -0
- package/dist/types/packages/desk/src/mat/tilt.d.ts +14 -0
- package/dist/types/packages/desk/src/nav/flight.d.ts +186 -0
- package/dist/types/packages/desk/src/nav/portal.d.ts +95 -0
- package/dist/types/packages/desk/src/object.d.ts +46 -0
- package/dist/types/packages/desk/src/pass-instrument.d.ts +89 -0
- package/dist/types/packages/desk/src/shaders.d.ts +12 -0
- package/dist/types/packages/desk/src/shaders.gen.d.ts +16 -0
- package/dist/types/packages/desk/src/submit-instrument.d.ts +36 -0
- package/dist/types/packages/desk/src/theme.d.ts +475 -0
- package/dist/types/packages/desk/src/tray/carry.d.ts +99 -0
- package/dist/types/packages/desk/src/tray/drawer.d.ts +113 -0
- package/dist/types/packages/desk/src/tray/flux.d.ts +46 -0
- package/dist/types/packages/desk/src/tray/index.d.ts +4 -0
- package/dist/types/packages/desk/src/tray/lattice.d.ts +71 -0
- package/dist/types/packages/desk/src/tray/layout.d.ts +7 -0
- package/dist/types/packages/desk/src/tray/look.d.ts +29 -0
- package/dist/types/packages/desk/src/tray/pass.d.ts +96 -0
- package/dist/types/packages/desk/src/tray/shaders.d.ts +10 -0
- package/dist/types/packages/desk/src/tray/specimens.d.ts +199 -0
- package/dist/types/packages/devtools/src/attach.d.ts +22 -8
- package/dist/types/packages/devtools/src/dock.d.ts +18 -14
- package/dist/types/packages/devtools/src/gpu-panel.d.ts +105 -0
- package/dist/types/packages/devtools/src/index.d.ts +5 -4
- package/dist/types/packages/dom/src/desk-host.d.ts +91 -0
- package/dist/types/packages/dom/src/host.d.ts +13 -14
- package/dist/types/packages/dom/src/index.d.ts +12 -10
- package/dist/types/packages/dom/src/loop.d.ts +18 -1
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +5 -29
- package/dist/types/packages/dom/src/widget-focus.d.ts +17 -13
- package/dist/types/packages/ice/src/desk-engine.d.ts +2 -0
- package/dist/types/packages/ice/src/desk-kit.d.ts +6 -0
- package/dist/types/packages/ice/src/desk-objects.d.ts +5 -0
- package/dist/types/packages/ice/src/desk.d.ts +5 -0
- package/dist/types/packages/kernel/src/atlas-pack.d.ts +157 -0
- package/dist/types/packages/kernel/src/bezier.d.ts +0 -15
- package/dist/types/packages/kernel/src/coords.d.ts +0 -39
- package/dist/types/packages/kernel/src/index.d.ts +2 -2
- package/dist/types/packages/kernel/src/nav-flight.d.ts +6 -0
- package/dist/types/packages/kernel/src/tray.d.ts +155 -0
- package/dist/types/packages/objects/src/board/board-pass.d.ts +180 -0
- package/dist/types/packages/objects/src/board/board-pen.d.ts +36 -0
- package/dist/types/packages/objects/src/board/board.d.ts +104 -0
- package/dist/types/packages/objects/src/board/data.d.ts +31 -0
- package/dist/types/packages/objects/src/board/history.d.ts +33 -0
- package/dist/types/packages/objects/src/board/kind.d.ts +208 -0
- package/dist/types/packages/objects/src/board/layout.d.ts +102 -0
- package/dist/types/packages/objects/src/board/object.d.ts +7 -0
- package/dist/types/packages/objects/src/board/pen.d.ts +71 -0
- package/dist/types/packages/objects/src/board/shaders.d.ts +36 -0
- package/dist/types/packages/objects/src/board/stroke.d.ts +99 -0
- package/dist/types/packages/objects/src/board/theme.d.ts +150 -0
- package/dist/types/packages/objects/src/calendar/data.d.ts +97 -0
- package/dist/types/packages/objects/src/calendar/events.d.ts +58 -0
- package/dist/types/packages/objects/src/calendar/hand.d.ts +53 -0
- package/dist/types/packages/objects/src/calendar/host/input.d.ts +67 -0
- package/dist/types/packages/objects/src/calendar/host/print.d.ts +9 -0
- package/dist/types/packages/objects/src/calendar/kind.d.ts +297 -0
- package/dist/types/packages/objects/src/calendar/law.d.ts +151 -0
- package/dist/types/packages/objects/src/calendar/layout.d.ts +47 -0
- package/dist/types/packages/objects/src/calendar/month.d.ts +75 -0
- package/dist/types/packages/objects/src/calendar/object.d.ts +45 -0
- package/dist/types/packages/objects/src/calendar/pad.d.ts +42 -0
- package/dist/types/packages/objects/src/calendar/pass.d.ts +229 -0
- package/dist/types/packages/objects/src/calendar/print.d.ts +87 -0
- package/dist/types/packages/objects/src/calendar/printing.d.ts +80 -0
- package/dist/types/packages/objects/src/calendar/roll.d.ts +45 -0
- package/dist/types/packages/objects/src/calendar/shaders.d.ts +12 -0
- package/dist/types/packages/objects/src/calendar/sheet.d.ts +43 -0
- package/dist/types/packages/objects/src/calendar/tiles.d.ts +67 -0
- package/dist/types/packages/objects/src/calendar/turn.d.ts +73 -0
- package/dist/types/packages/objects/src/calendar/writing.d.ts +42 -0
- package/dist/types/packages/objects/src/index.d.ts +22 -0
- package/dist/types/packages/objects/src/kinds.d.ts +24 -0
- package/dist/types/packages/objects/src/minimat/inside.d.ts +17 -0
- package/dist/types/packages/objects/src/minimat/kind.d.ts +51 -0
- package/dist/types/packages/objects/src/minimat/layout.d.ts +83 -0
- package/dist/types/packages/objects/src/minimat/minimat.d.ts +179 -0
- package/dist/types/packages/objects/src/minimat/object.d.ts +8 -0
- package/dist/types/packages/objects/src/minimat/pass.d.ts +61 -0
- package/dist/types/packages/objects/src/minimat/shaders.d.ts +10 -0
- package/dist/types/packages/objects/src/minimat/theme.d.ts +98 -0
- package/dist/types/packages/objects/src/notebook/ink.d.ts +70 -0
- package/dist/types/packages/objects/src/notebook/kind.d.ts +236 -0
- package/dist/types/packages/objects/src/notebook/law.d.ts +129 -0
- package/dist/types/packages/objects/src/notebook/layout.d.ts +46 -0
- package/dist/types/packages/objects/src/notebook/leaf.d.ts +44 -0
- package/dist/types/packages/objects/src/notebook/mesh.d.ts +20 -0
- package/dist/types/packages/objects/src/notebook/motion.d.ts +107 -0
- package/dist/types/packages/objects/src/notebook/object.d.ts +8 -0
- package/dist/types/packages/objects/src/notebook/pages.d.ts +56 -0
- package/dist/types/packages/objects/src/notebook/pass.d.ts +197 -0
- package/dist/types/packages/objects/src/notebook/pick.d.ts +30 -0
- package/dist/types/packages/objects/src/notebook/raster.d.ts +28 -0
- package/dist/types/packages/objects/src/notebook/shaders.d.ts +11 -0
- package/dist/types/packages/objects/src/notebook/shape.d.ts +155 -0
- package/dist/types/packages/objects/src/notebook/theme.d.ts +76 -0
- package/dist/types/packages/objects/src/palette.d.ts +577 -0
- package/dist/types/packages/objects/src/paper/host/body.d.ts +30 -0
- package/dist/types/packages/objects/src/paper/kind.d.ts +81 -0
- package/dist/types/packages/objects/src/paper/layout.d.ts +73 -0
- package/dist/types/packages/objects/src/paper/object.d.ts +31 -0
- package/dist/types/packages/objects/src/paper/pages.d.ts +43 -0
- package/dist/types/packages/objects/src/paper/paper-pass.d.ts +86 -0
- package/dist/types/packages/objects/src/paper/paper.d.ts +117 -0
- package/dist/types/packages/objects/src/paper/shaders.d.ts +18 -0
- package/dist/types/packages/objects/src/paper/theme.d.ts +58 -0
- package/dist/types/packages/objects/src/paper/typing.d.ts +51 -0
- package/dist/types/packages/objects/src/paper/writing.d.ts +161 -0
- package/dist/types/packages/objects/src/photo/carry.d.ts +33 -0
- package/dist/types/packages/objects/src/photo/kind.d.ts +136 -0
- package/dist/types/packages/objects/src/photo/layout.d.ts +39 -0
- package/dist/types/packages/objects/src/photo/object.d.ts +5 -0
- package/dist/types/packages/objects/src/photo/photo-pass.d.ts +104 -0
- package/dist/types/packages/objects/src/photo/photo.d.ts +262 -0
- package/dist/types/packages/objects/src/photo/pictures.d.ts +152 -0
- package/dist/types/packages/objects/src/photo/sample.d.ts +9 -0
- package/dist/types/packages/objects/src/photo/shaders.d.ts +18 -0
- package/dist/types/packages/objects/src/preset.d.ts +44 -0
- package/dist/types/packages/objects/src/shaders.d.ts +3 -0
- package/dist/types/packages/objects/src/shaders.gen.d.ts +21 -0
- package/dist/types/packages/react/src/canvas-hooks.d.ts +12 -0
- package/dist/types/packages/react/src/desk.d.ts +56 -0
- package/dist/types/packages/react/src/engine-context.d.ts +2 -2
- package/dist/types/packages/react/src/frame-preview-boundary.d.ts +19 -0
- package/dist/types/packages/react/src/hooks.d.ts +9 -14
- package/dist/types/packages/react/src/index.d.ts +16 -9
- package/dist/types/packages/react/src/keymap.d.ts +21 -2
- package/dist/types/packages/react/src/selection-menu.d.ts +171 -0
- package/dist/types/packages/react/src/tray-bar.d.ts +124 -0
- package/package.json +64 -31
- package/dist/chunk-5FQS7V2C.js.map +0 -1
- package/dist/chunk-JWRHAM7L.js +0 -1163
- package/dist/chunk-JWRHAM7L.js.map +0 -1
- package/dist/chunk-MFTMPJUF.js +0 -681
- package/dist/chunk-MFTMPJUF.js.map +0 -1
- package/dist/chunk-OR5FNVUE.js.map +0 -1
- package/dist/ground.js +0 -1213
- package/dist/ground.js.map +0 -1
- package/dist/r3f.js +0 -1644
- package/dist/r3f.js.map +0 -1
- package/dist/types/packages/core/src/input/measure-queue.d.ts +0 -30
- package/dist/types/packages/core/src/settings/ground-config.d.ts +0 -103
- package/dist/types/packages/core/src/systems/measure-ingest.d.ts +0 -26
- package/dist/types/packages/devtools/src/gl-panel.d.ts +0 -69
- package/dist/types/packages/dom/src/measure-adapter.d.ts +0 -31
- package/dist/types/packages/dom/src/measure-wiring.d.ts +0 -49
- package/dist/types/packages/dom/src/planes.d.ts +0 -33
- package/dist/types/packages/dom/src/reflectors/chrome.d.ts +0 -33
- package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +0 -59
- package/dist/types/packages/dom/src/reflectors/graybox.d.ts +0 -31
- package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +0 -38
- package/dist/types/packages/ground/src/index.d.ts +0 -69
- package/dist/types/packages/ground/src/layer.d.ts +0 -40
- package/dist/types/packages/ground/src/pass.d.ts +0 -41
- package/dist/types/packages/ground/src/passes/grid-classic.d.ts +0 -30
- package/dist/types/packages/ground/src/passes/grid-magnet.d.ts +0 -31
- package/dist/types/packages/ground/src/passes/grid.d.ts +0 -28
- package/dist/types/packages/ground/src/passes/guides-collect.d.ts +0 -14
- package/dist/types/packages/ground/src/passes/guides.d.ts +0 -3
- package/dist/types/packages/ground/src/passes/magnet-collect.d.ts +0 -63
- package/dist/types/packages/ground/src/passes/soup-collect.d.ts +0 -35
- package/dist/types/packages/ground/src/passes/soup-mesh.d.ts +0 -8
- package/dist/types/packages/ground/src/passes/wires-collect.d.ts +0 -35
- package/dist/types/packages/ground/src/passes/wires.d.ts +0 -10
- package/dist/types/packages/ground/src/poles.d.ts +0 -47
- package/dist/types/packages/ground/src/renderer.d.ts +0 -15
- package/dist/types/packages/ice/src/ground.d.ts +0 -1
- package/dist/types/packages/ice/src/r3f.d.ts +0 -1
- package/dist/types/packages/kernel/src/eviction.d.ts +0 -23
- package/dist/types/packages/kernel/src/zoom-bands.d.ts +0 -19
- package/dist/types/packages/r3f/src/bridge.d.ts +0 -129
- package/dist/types/packages/r3f/src/composite-material.d.ts +0 -26
- package/dist/types/packages/r3f/src/compositor-pass.d.ts +0 -105
- package/dist/types/packages/r3f/src/dev-write-trap.d.ts +0 -30
- package/dist/types/packages/r3f/src/gl-root.d.ts +0 -96
- package/dist/types/packages/r3f/src/gl-router.d.ts +0 -70
- package/dist/types/packages/r3f/src/index.d.ts +0 -17
- package/dist/types/packages/r3f/src/island-state.d.ts +0 -112
- package/dist/types/packages/r3f/src/island.d.ts +0 -13
- package/dist/types/packages/r3f/src/pool.d.ts +0 -56
- package/dist/types/packages/r3f/src/preview-capture.d.ts +0 -19
- package/dist/types/packages/r3f/src/resource-registry.d.ts +0 -30
- package/dist/types/packages/r3f/src/self-sustain.d.ts +0 -39
- package/dist/types/packages/r3f/src/use-island-frame.d.ts +0 -39
- package/dist/types/packages/react/src/infinite-canvas.d.ts +0 -140
- package/dist/types/packages/react/src/preview-snapshots.d.ts +0 -17
- package/dist/types/packages/react/src/widget-preview.d.ts +0 -14
- package/dist/types/packages/react/src/widget-root.d.ts +0 -33
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const WGSL: {
|
|
2
|
+
readonly "hold/hold.wgsl": "// THE HAND'S FOCUS (design-015 §8; D4b) — the desk behind the object in hand goes out of focus, and the\n// object is laid over it in its own light. Four fullscreen fragments over one triangle, one binding set:\n// - `fs_down` / `fs_up`: the dual-Kawase blur (Bjørge, ARM 2015) — a down pass takes the centre ×4 and the\n// four diagonals at ±offset half-texels; an up pass eight taps at ±offset (the axes once, the diagonals\n// twice) — the desk's HALF-SIZE copy blurred through two levels and back, once per settled desk.\n// - `fs_desk`: the frame's background — the sharp half-size copy mixed with the blurred one by the carry\n// amount (half-way up, half the blur), dimmed by `dim` (8 % at the top).\n// - `fs_hand`: the object in hand, drawn into a transparent target with the kinds' own straight-alpha\n// \"source over\" — which, from a cleared target, accumulates PREMULTIPLIED colour — laid over the frame\n// (one, one − src alpha), through the READING LIGHT (Q-n): saturate then brighten, both linear in the\n// premultiplied colour, so the at-rest passes' bytes never move.\n// Pure of everything but its bindings (engine/shader.ts's rule): the CPU decides every number.\n\n@group(0) @binding(0) var<uniform> u: HoldUniforms;\n@group(0) @binding(1) var tex_a: texture_2d<f32>;\n@group(0) @binding(2) var tex_b: texture_2d<f32>;\n@group(0) @binding(3) var samp: sampler;\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) uv: vec2f,\n}\n\n/** One triangle over the whole attachment; uv (0, 0) at the top-left, as the texture's rows run. */\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var out: VSOut;\n let x = f32((vid << 1u) & 2u);\n let y = f32(vid & 2u);\n out.clip = vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n out.uv = vec2f(x, y);\n return out;\n}\n\n/** Dual Kawase, down: `u.texel.xy` = one texel of the SOURCE, `u.texel.z` = the offset in half-texels. */\n@fragment\nfn fs_down(in: VSOut) -> @location(0) vec4f {\n let o = u.texel.xy * 0.5 * u.texel.z;\n var c = textureSample(tex_a, samp, in.uv) * 4.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, -o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, -o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, o.y));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, o.y));\n return c / 8.0;\n}\n\n/** Dual Kawase, up: the same offset, eight taps. */\n@fragment\nfn fs_up(in: VSOut) -> @location(0) vec4f {\n let o = u.texel.xy * 0.5 * u.texel.z;\n var c = textureSample(tex_a, samp, in.uv + vec2f(-o.x * 2.0, 0.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(0.0, o.y * 2.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x * 2.0, 0.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(o.x, -o.y)) * 2.0;\n c += textureSample(tex_a, samp, in.uv + vec2f(0.0, -o.y * 2.0));\n c += textureSample(tex_a, samp, in.uv + vec2f(-o.x, -o.y)) * 2.0;\n return c / 12.0;\n}\n\n/** The desk out of focus: the sharp copy (tex_a) mixed with the blurred one (tex_b) by the carry `u.mix.x`, dimmed by `u.mix.y`. Opaque. */\n@fragment\nfn fs_desk(in: VSOut) -> @location(0) vec4f {\n let sharp = textureSample(tex_a, samp, in.uv).rgb;\n let soft = textureSample(tex_b, samp, in.uv).rgb;\n return vec4f(mix(sharp, soft, u.mix.x) * (1.0 - u.mix.y), 1.0);\n}\n\n/** The hand over the frame: premultiplied (tex_a), through the reading light — saturate `u.mix.z`, brightness `u.mix.w`. Blend (one, one − src alpha). */\n@fragment\nfn fs_hand(in: VSOut) -> @location(0) vec4f {\n let h = textureSample(tex_a, samp, in.uv);\n let lum = dot(h.rgb, vec3f(0.2126, 0.7152, 0.0722));\n let rgb = mix(vec3f(lum), h.rgb, u.mix.z) * u.mix.w;\n return vec4f(rgb, h.a);\n}\n";
|
|
3
|
+
readonly "kit/book.wgsl": "// (The kit's since K4a, design-016 §5 — moved from notebook/notebook.wgsl: the desk calendar composes it too, `kitWgsl([..., \"book\"])`.)\n//\n// The NOTEBOOK — a real 3D book under the desk's one lamp (NOTEBOOK.md). A PURE module: the\n// uniform blocks, the book records and the textures arrive as parameters, and it names no\n// colour (a book's palette is its record's). mat.wgsl comes first: its noise, its colour chain\n// (`shade_mat` by day, `night_mat` by night) and its gobo (`sample_gobo`) are the desk's, so a\n// page under the palm is dappled and graded exactly as the mat beside it.\n//\n// The pieces: the desk eye's projection (`nb_clip`, eye.ts is the same arithmetic); the\n// shadow — PCSS on the book's own shadow map, whose penumbra grows with the gap between caster\n// and receiver by the desk's law (σ = σ0 + k·height, the note's and the photo's); the\n// materials — bookcloth with a weave and a sheen, the cover's printed design, the ivory page\n// with its fibre, tooth, cockle and dots, the stacked edges of the sheets, the endpaper framed\n// by the cloth's turn-in; and the light — a lambert normalised so a flat face reads its own\n// albedo, the sky's share where the lamp is blocked, the leaves' dapple.\n\nconst NB_PI = 3.14159265;\n\nfn nb_sat(x: f32) -> f32 { return clamp(x, 0.0, 1.0); }\n\n// ---- the desk eye: a world point → clip space (x, y 2·zoom·(p − E)/viewport; w = (H − z)/H; a perspective depth)\nfn nb_clip(k: NbUniforms, P: vec3f) -> vec4f {\n let H = k.eye.z;\n let d = H - P.z;\n let x = 2.0 * k.eye.w * (P.x - k.eye.x) / k.view.x;\n let y = -2.0 * k.eye.w * (P.y - k.eye.y) / k.view.y;\n let n = k.view.z;\n let f = k.view.w;\n let z = (d - n) * f / ((f - n) * H);\n return vec4f(x, y, z, d / H);\n}\n\n// A world point raised off the desk, in the projector's frame (mat.wgsl `desk_of` with a height).\nfn nb_desk_at(u: MatUniforms, P: vec3f) -> vec3f {\n return vec3f(u.plane.x + P.x * u.plane.z, u.plane.w + P.z * u.plane.z, u.plane.y + P.y * u.plane.z);\n}\n\n// ---- noise in a face's own world units: an integer hash (no sin — a page fragment reads a dozen of these),\n// value noise with its analytic gradient (so a bump costs one evaluation, not three)\nfn nb_hash(p: vec2i) -> f32 {\n var h = (u32(p.x) * 668265261u) ^ (u32(p.y) * 374761393u);\n h = (h ^ (h >> 15u)) * 2246822519u;\n h = (h ^ (h >> 13u)) * 3266489917u;\n h = h ^ (h >> 16u);\n return f32(h) * 2.3283064e-10;\n}\n\n// value (centred on 0), d/dx, d/dy\nfn nb_vn(p: vec2f) -> vec3f {\n let fl = floor(p);\n let i = vec2i(fl);\n let f = p - fl;\n let w = f * f * (3.0 - 2.0 * f);\n let dw = 6.0 * f * (1.0 - f);\n let a = nb_hash(i);\n let b = nb_hash(i + vec2i(1, 0));\n let c = nb_hash(i + vec2i(0, 1));\n let d = nb_hash(i + vec2i(1, 1));\n let k = a - b - c + d;\n return vec3f(a + (b - a) * w.x + (c - a) * w.y + k * w.x * w.y - 0.5, dw.x * (b - a + k * w.y), dw.y * (c - a + k * w.x));\n}\n\n// three octaves, the gradient carried through\nfn nb_fbm(p: vec2f) -> vec3f {\n let a = nb_vn(p);\n let b = nb_vn(p * 2.03 + vec2f(17.1, 3.7));\n let c = nb_vn(p * 4.11 + vec2f(-5.3, 11.9));\n return vec3f(a.x + b.x * 0.5 + c.x * 0.25, a.yz + b.yz * 1.015 + c.yz * 1.0275);\n}\n\n// A pattern's detail faded as its period falls under the pixel: 1 while `period` spans ≥ 3 px of `px` world, 0 under 1.\nfn nb_detail(period: f32, px: f32) -> f32 { return smoothstep(1.2, 3.0, period / max(px, 1e-5)); }\n\n// Coverage of a signed distance (negative inside) at a pixel of `px` world units.\nfn nb_cov(d: f32, px: f32) -> f32 { return nb_sat(0.5 - d / max(px, 1e-5)); }\n\n// A rounded box, centred, half extents `b`, radius `r`.\nfn nb_sd_box(p: vec2f, b: vec2f, r: f32) -> f32 {\n let q = abs(p) - b + vec2f(r);\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - r;\n}\n\n// A face [0, w] × [0, h] whose corners at u = w are rounded by r (the spine side square): signed distance.\nfn nb_sd_fore(q: vec2f, w: f32, h: f32, r: f32) -> f32 {\n // mirror the square side out of reach: extend the box past u = 0 by r\n let c = vec2f((w - r) * 0.5, h * 0.5);\n let b = vec2f((w + r) * 0.5, h * 0.5);\n return nb_sd_box(q - c, b, r);\n}\n\n// ---- the shadow: PCSS on the book's own map (the blocker's gap → the desk's penumbra law)\n// The golden angle as a rotation: a Vogel spiral's next direction is this times the last.\nconst NB_GOLDEN = mat2x2f(-0.7373688, 0.6754903, -0.6754903, -0.7373688);\n\nfn nb_shadow(k: NbUniforms, B: NbBook, P: vec3f, n: vec3f, rot: f32, tex: texture_depth_2d_array, cmp: sampler_comparison) -> f32 {\n if (B.sh.z < 0.5 || (u32(k.ring.y) & 1u) != 0u) { return 1.0; }\n let texel = B.sh.x;\n let res = k.shadow2.z;\n let layer = i32(B.lamp.w);\n let Po = P + n * texel * 1.5;\n let lc = B.light * vec4f(Po, 1.0);\n let uv = vec2f(lc.x * 0.5 + 0.5, 0.5 - lc.y * 0.5);\n if (uv.x <= 0.0 || uv.y <= 0.0 || uv.x >= 1.0 || uv.y >= 1.0) { return 1.0; }\n // nothing was drawn at 1 (the clear): a receiver compares just inside it, so \"no caster here\" is lit wherever it lies\n let depth = min(lc.z, 0.99995);\n let bias = 0.35 / max(B.sh.y, 1.0);\n // the blocker search: as far as the widest penumbra this book can cast\n let widest = k.shadow.x + k.shadow.y * B.sh.w;\n let search = clamp(2.2 * widest / texel, 2.0, 72.0);\n var sum = 0.0;\n var cnt = 0.0;\n let base = uv * res;\n var dir = vec2f(cos(rot), sin(rot));\n for (var i = 0; i < 10; i++) {\n let o = dir * sqrt((f32(i) + 0.5) / 10.0) * search;\n dir = NB_GOLDEN * dir;\n let t = clamp(vec2i(base + o), vec2i(0), vec2i(i32(res) - 1));\n let d = textureLoad(tex, t, layer, 0);\n if (d < depth - bias) { sum += d; cnt += 1.0; }\n }\n if (cnt < 0.5) { return 1.0; }\n let gap = (depth - sum / cnt) * B.sh.y * B.lamp.z; // the blocker's height over the receiver, world units\n let sigma = k.shadow.x + k.shadow.y * max(gap, 0.0);\n let r = clamp(sigma / texel * 1.7, 0.75, 72.0);\n var lit = 0.0;\n var wsum = 0.0;\n dir = vec2f(-dir.y, dir.x);\n let inv2s2 = 1.0 / (2.0 * (r / 1.7) * (r / 1.7) + 1e-4);\n for (var i = 0; i < 14; i++) {\n let o = dir * sqrt((f32(i) + 0.5) / 14.0) * r;\n dir = NB_GOLDEN * dir;\n let w = exp(-dot(o, o) * inv2s2);\n lit += w * textureSampleCompareLevel(tex, cmp, (base + o) / res, layer, depth - bias);\n wsum += w;\n }\n return lit / max(wsum, 1e-5);\n}\n\n// ---- the colour chain: the lamp's light on a face, then the desk's day or night\nfn nb_colour(u: MatUniforms, albedo: vec3f, light: f32, gobo: f32, noise: f32) -> vec3f {\n let day = shade_mat(u, clamp(albedo * pow(max(light, 0.0), 1.0 / 2.2), vec3f(0.0), vec3f(1.0)), gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = night_mat(u, srgb_to_linear(clamp(albedo, vec3f(0.0), vec3f(1.0))) * max(light, 0.0), gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// ---- the cloth: a plain weave as a height, with its threads' own shades\nstruct NbCloth { h: f32, g: vec2f, shade: f32 }\nfn nb_cloth(q: vec2f, pitch: f32, px: f32) -> NbCloth {\n let fade = 1.0 - smoothstep(0.35, 0.9, px / pitch);\n let a = q / pitch;\n let cell = floor(a);\n let over = ((i32(cell.x) + i32(cell.y)) & 1) == 0;\n let f = fract(a) - vec2f(0.5);\n // warp (along v) over weft (along u) on alternate cells: a raised thread across each cell\n let warp = 1.0 - 4.0 * f.x * f.x;\n let weft = 1.0 - 4.0 * f.y * f.y;\n let h = select(weft, warp, over) * 0.8 + 0.2 * (warp * weft);\n let dwarp = -8.0 * f.x / pitch;\n let dweft = -8.0 * f.y / pitch;\n let g = select(vec2f(0.0, dweft), vec2f(dwarp, 0.0), over) * 0.8 + 0.2 * vec2f(dwarp * weft, warp * dweft);\n // the threads' slubs: each thread a little thicker or lighter along its run\n let slub = nb_vn(vec2f(select(cell.y, cell.x, over) * 1.7, select(a.x, a.y, over) * 0.21)).x;\n var out: NbCloth;\n out.h = (h - 0.55 + slub * 0.35) * fade;\n out.g = g * fade;\n out.shade = (slub * 0.9 + (h - 0.55) * 0.35) * fade;\n return out;\n}\n\n// ---- the cover's DESIGNS, printed on the cloth past the spine band (u, v in the face's world units from the band's edge and the head)\nfn nb_ring(d: f32, r: f32, w: f32, px: f32) -> f32 { return nb_cov(abs(d - r) - w * 0.5, px); }\n\nfn nb_hash2(p: vec2f) -> f32 { return hash12(p * vec2f(0.1031, 0.1030) + vec2f(19.19, 7.7)); }\n\n// 0 plain cloth · 1 orbit (rings round a brass sun) · 2 tiles (a grid of quarter-rounds, halves and dots) · 3 label (a cream label on kraft) · 4 bordered (a blind-pressed frame)\nfn nb_design(B: NbBook, q: vec2f, w: f32, h: f32, px: f32, base: vec3f) -> vec4f {\n let id = i32(B.look.x + 0.5);\n var c = base;\n var press = 0.0; // a blind line pressed into the cloth (a height)\n if (id == 1) {\n let o = vec2f(w * 0.5, h * 0.47);\n let d = length(q - o);\n let ringW = 0.9;\n for (var i = 1; i <= 6; i++) {\n let r = f32(i) * min(w, h) * 0.075 + 4.0;\n c = mix(c, B.col3.rgb, nb_ring(d, r, ringW, px) * 0.8);\n // a planet on each ring\n let a = nb_hash2(vec2f(f32(i), 3.0)) * 6.2831 + B.look.w;\n let pc = o + vec2f(cos(a), sin(a)) * r;\n let pr = 1.6 + 2.4 * nb_hash2(vec2f(f32(i), 9.0));\n let pcol = select(select(B.col4.rgb, B.col2.rgb, i % 3 == 1), B.col3.rgb, i % 3 == 2);\n c = mix(c, pcol, nb_cov(length(q - pc) - pr, px));\n }\n c = mix(c, B.col2.rgb, nb_cov(d - 9.0, px));\n } else if (id == 2) {\n let n = vec2f(3.0, 4.0);\n let cs = vec2f(w, h) / n;\n let cell = clamp(floor(q / cs), vec2f(0.0), n - vec2f(1.0));\n let f = (q - cell * cs) / cs; // 0..1 in the tile\n let r = nb_hash2(cell + vec2f(B.look.w * 7.0, 1.0));\n let r2 = nb_hash2(cell + vec2f(5.0, B.look.w * 3.0));\n let bgI = i32(r2 * 4.0);\n let fgI = (bgI + 1 + i32(r * 3.0)) % 4;\n var bg = B.col2.rgb; var fg = B.col3.rgb;\n if (bgI == 1) { bg = B.col3.rgb; } else if (bgI == 2) { bg = B.col4.rgb; } else if (bgI == 3) { bg = base; }\n if (fgI == 0) { fg = B.col2.rgb; } else if (fgI == 2) { fg = B.col4.rgb; } else if (fgI == 3) { fg = base; }\n let pxt = px / min(cs.x, cs.y);\n var m = 0.0;\n let kind = i32(r * 5.0);\n if (kind == 0) { m = nb_cov(length(f) - 1.0, pxt); } // a quarter-round from a corner\n else if (kind == 1) { m = nb_cov(length(f - vec2f(0.5, 1.0)) - 0.5, pxt); } // a half-round on the edge\n else if (kind == 2) { m = nb_cov((f.x - f.y) * 0.7071, pxt); } // a diagonal half\n else if (kind == 3) { m = nb_cov(length(f - vec2f(0.5)) - 0.3, pxt); } // a dot\n else { m = nb_cov(max(abs(f.x - 0.5), abs(f.y - 0.5)) - 0.22, pxt); } // a square\n c = mix(bg, fg, m);\n } else if (id == 3) {\n let lo = vec2f(w * 0.16, h * 0.16);\n let hi = vec2f(w * 0.84, h * 0.33);\n let d = nb_sd_box(q - (lo + hi) * 0.5, (hi - lo) * 0.5, 3.0);\n let lab = nb_cov(d, px);\n var lc = B.col3.rgb;\n // two ruled lines on the label for a name\n let ly = (q.y - lo.y) / (hi.y - lo.y);\n let rule = max(nb_cov(abs(ly - 0.55) * (hi.y - lo.y) - 0.3, px), nb_cov(abs(ly - 0.8) * (hi.y - lo.y) - 0.3, px)) * step(lo.x + 6.0, q.x) * step(q.x, hi.x - 6.0);\n lc = mix(lc, B.col7.rgb, rule * 0.35);\n c = mix(c, lc, lab);\n } else if (id == 4) {\n let d = nb_sd_box(q - vec2f(w, h) * 0.5, vec2f(w, h) * 0.5 - vec2f(9.0), 2.5);\n press = -exp(-(d * d) / 0.6) * 0.5;\n c = c * (1.0 - exp(-(d * d) / 0.6) * 0.08);\n }\n return vec4f(c, press);\n}\n\n// ---- the paper: its albedo (fibre, mottle) and its height (tooth, cockle), in the page's world units\nfn nb_paper_seed(seed: f32) -> vec2f { return vec2f(fract(seed * 0.618034) * 97.0, fract(seed * 0.381966) * 89.0); }\n\n// The paper from its baked texture (src/notebook/paper-tex.ts): the mottle and the fibres over a 64-unit\n// tile, the tooth's slope over a 9.6-unit one, each page's tiles offset by its number; the cockle, slow\n// enough for one noise. Explicit gradients, so it may be read anywhere and filters by its mips.\nstruct NbPaperS { albedo: vec3f, g: vec2f }\nfn nb_paper(k: NbUniforms, base: vec3f, q: vec2f, dqdx: vec2f, dqdy: vec2f, seed: f32, tex: texture_2d<f32>, samp: sampler) -> NbPaperS {\n let s = nb_paper_seed(seed);\n let a = textureSampleGrad(tex, samp, q / 64.0 + s / 64.0, dqdx / 64.0, dqdy / 64.0);\n let tt = 9.6;\n let b = textureSampleGrad(tex, samp, q / tt + s.yx / 31.0, dqdx / tt, dqdy / tt);\n let v = (a.r - 0.5) * k.paper.y * 2.0 + (a.g - 0.5) * k.paper.x * 3.0;\n var out: NbPaperS;\n out.albedo = base + vec3f(v * 1.05, v, v * 0.8);\n let tooth = (b.ba - vec2f(0.5)) * (2.0 * 48.0 / tt) * (k.paper.z * 0.02);\n let c = nb_vn(q / 55.0 + s * 0.5);\n out.g = tooth + c.yz / 55.0 * (k.paper.w * 2.0);\n return out;\n}\n\n// The ruling printed on a page: dots on a pitch, inside a margin, faded as they fall under the pixel.\nfn nb_ruling(k: NbUniforms, B: NbBook, q: vec2f, w: f32, h: f32, px: f32) -> f32 {\n let kind = i32(B.look.y + 0.5);\n if (kind == 0) { return 0.0; }\n let pitch = k.rule.x;\n let m = k.rule.z;\n let inside = step(m, q.x) * step(q.x, w - m) * step(m, q.y) * step(q.y, h - m);\n let vis = smoothstep(2.0, 5.0, pitch / px);\n // the grid is laid from the page's head and fore-edge so it sits the same on every page\n let g = (q - vec2f(w, 0.0)) / pitch;\n let f = fract(g) - vec2f(0.5);\n if (kind == 1) {\n let d = length(f) * pitch - k.rule.y;\n return nb_cov(d, max(px, 0.35)) * inside * vis;\n }\n if (kind == 2) {\n let d = abs(f.y) * pitch - k.rule.y * 0.45;\n return nb_cov(d, max(px, 0.3)) * inside * vis * 0.75;\n }\n let d = min(abs(f.x), abs(f.y)) * pitch - k.rule.y * 0.4;\n return nb_cov(d, max(px, 0.3)) * inside * vis * 0.6;\n}\n\n// ---- the case: the cover's face (u from the spine edge, v from the head, world units)\n\n// Its height's slope (d/du, d/dv): the cloth rolling over the board's edge (the bevel), the groove\n// where the band's edge lies under the cover's cloth, the hinge's pressed groove, the weave, a blind press.\nfn nb_cover_g(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec2f {\n let w = B.size.x;\n let h = B.size.y;\n let e = 0.05;\n let d = nb_sd_fore(q, w, h, B.size.w);\n let dg = vec2f(nb_sd_fore(q + vec2f(e, 0.0), w, h, B.size.w) - d, nb_sd_fore(q + vec2f(0.0, e), w, h, B.size.w) - d) / e;\n // bevel: z = −1.1·(1 + d/2.2)² over the last 2.2 units — its slope along the outline's normal\n let t = nb_sat(1.0 + d / 2.2);\n var g = dg * (-2.2 * t / 2.2) * select(0.0, 1.0, d > -2.2 && d < 0.0);\n let band = B.page.w;\n let xb = q.x - band;\n g.x += 0.22 * exp(-(xb * xb) / 0.5) * (2.0 * xb / 0.5);\n let xh = q.x - 7.0;\n g.x += 0.55 * exp(-(xh * xh) / 2.2) * (2.0 * xh / 2.2);\n if (B.foot.y < 0.5) { g += nb_cloth(q, k.cloth.x, px).g * k.cloth.y; }\n else { g += nb_fbm(q * 1.1).yz * 1.1 * 0.05 * nb_detail(1.0, px); }\n if (i32(B.look.x + 0.5) == 4 && q.x > band) {\n // the blind-pressed frame\n let p = q - vec2f(band, 0.0);\n let hw = vec2f(w - band, h) * 0.5;\n let db = nb_sd_box(p - hw, hw - vec2f(9.0), 2.5);\n let dbg = vec2f(nb_sd_box(p + vec2f(e, 0.0) - hw, hw - vec2f(9.0), 2.5) - db, nb_sd_box(p + vec2f(0.0, e) - hw, hw - vec2f(9.0), 2.5) - db) / e;\n g += dbg * (0.5 * exp(-(db * db) / 0.6) * (2.0 * db / 0.6));\n }\n return g;\n}\n\nfn nb_cover_albedo(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec3f {\n let w = B.size.x;\n let h = B.size.y;\n let band = B.page.w;\n let design = nb_design(B, q - vec2f(band, 0.0), w - band, h, px, B.col0.rgb).rgb;\n var c = mix(design, B.col1.rgb, nb_cov(q.x - band, px));\n if (B.foot.y < 0.5 || q.x < band) { c *= 1.0 + nb_cloth(q, k.cloth.x, px).shade * 0.16; }\n else { c += vec3f(nb_fbm(q * 0.35).x * 0.035 + nb_fbm(q * 2.1).x * 0.02 * nb_detail(0.6, px)); }\n return c;\n}\n\n// The inside of a board: the endpaper, framed by the cloth's turn-in along the head, the fore-edge and the tail.\nstruct NbEnd { albedo: vec3f, ao: f32 }\nfn nb_endpaper(k: NbUniforms, B: NbBook, q: vec2f, px: f32, stacked: bool, dqdx: vec2f, dqdy: vec2f, tex: texture_2d<f32>, samp: sampler) -> NbEnd {\n let w = B.size.x;\n let h = B.size.y;\n let turn = 4.2;\n let de = nb_sd_fore(q - vec2f(-2.0, turn), w - turn + 2.0, h - 2.0 * turn, max(B.size.w - turn * 0.6, 1.0));\n let inside = nb_cov(de, px);\n var cloth = B.col0.rgb;\n if (B.foot.y < 0.5) { cloth *= 1.0 + nb_cloth(q, k.cloth.x, px).shade * 0.16; }\n let paper = nb_paper(k, B.col5.rgb, q, dqdx, dqdy, 91.0 + B.look.w, tex, samp).albedo;\n var out: NbEnd;\n out.albedo = mix(cloth, paper, inside);\n // the endpaper's edge: a hairline of shade where the paper lies over the cloth\n out.ao = 1.0 - 0.3 * exp(-(de * de) / 0.35) * (1.0 - inside * 0.4);\n if (stacked) {\n // the text block standing on it: occlusion at its foot\n let sq = B.open.w;\n let ds = nb_sd_fore(q - vec2f(-4.0, sq), w - sq + 4.0, h - 2.0 * sq, B.page.z);\n out.ao *= 1.0 - 0.34 * exp(-max(ds, 0.0) / 2.6);\n }\n return out;\n}\n\n// A board's edge: the cloth wrapped over it — the band's near the spine, the cover's beyond.\nfn nb_board(k: NbUniforms, B: NbBook, q: vec2f, px: f32) -> vec3f {\n let w = B.size.x;\n let h = B.size.y;\n let r = B.size.w;\n let total = 2.0 * (w - r) + NB_PI * r + h - 2.0 * r;\n let band = B.page.w;\n let inBand = max(1.0 - smoothstep(band - 0.5, band + 0.5, q.x), smoothstep(total - band - 0.5, total - band + 0.5, q.x));\n var c = mix(B.col0.rgb, B.col1.rgb, inBand);\n c *= 0.94 + nb_cloth(vec2f(q.x, q.y * 3.0), k.cloth.x, px).shade * 0.12;\n return c;\n}\n\n// A stack's edge: the sheets' edges, one stripe each, each sheet a shade of its own; averaged as they fall under the pixel.\nfn nb_edge(k: NbUniforms, B: NbBook, q: vec2f, sheets: f32, side: f32, px: f32) -> vec3f {\n let t = max(B.foot.z, 1e-3);\n let f = q.y / t;\n let i = floor(f);\n let g = fract(f);\n let vis = smoothstep(0.7, 2.2, t / px);\n let own = hash12(vec2f(i + side * 131.0, 7.0)) - 0.5;\n let gap = pow(1.0 - min(g, 1.0 - g) * 2.0, 6.0);\n let rough = nb_vn(vec2f(q.x * 0.35, i * 1.7)).x * 0.05;\n let shade = 0.9 + (own * 0.08 + rough) * vis - gap * 0.3 * vis;\n return B.col6.rgb * shade;\n}\n";
|
|
4
|
+
readonly "kit/composite.wgsl": "// (The kit's since K4a — moved from notebook/notebook-composite.wgsl: every layered kind lays its layer with it, kit/layer.ts `layerComposite`.)\n// A layered kind's layer onto the canvas: the resolved, premultiplied layer, over what the ground drew — through the slot's PORTAL CHAIN\n// (design-018 §4 — the kit's contract, which the kind's own passes cannot keep: they draw into the layer, not onto the canvas). The\n// whole layer is laid at the chain's cover, so its object fades as ONE (a book's cover never shows its pages through it — group\n// opacity, the tray's feather); at the root the chain is empty, the cover exactly 1, and the layer is laid as it always was.\n// K7a: the layer is its screen BOX's (kit/layer.ts `BoxTargets`) — the texel of a pixel is the pixel less the box's origin.\n\n@group(0) @binding(0) var layer: texture_2d<f32>;\n@group(0) @binding(1) var<uniform> origin: vec4f;\n@group(0) @binding(2) var<uniform> u: MatUniforms;\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n return vec4f(p[vid], 0.0, 1.0);\n}\n\n@fragment\nfn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n let dpr = u.cam.w;\n return textureLoad(layer, vec2i(pos.xy) - vec2i(origin.xy), 0) * portal_cover(pos.xy / dpr, u.portals, u.clips, dpr);\n}\n";
|
|
5
|
+
readonly "marks/marks-pass.wgsl": "// The marks pass — the desk's chrome over every stratum (design-015 §4.2 stratum 5, §7; D4a): one\n// instanced draw, one quad per mark over the screen rect the CPU gave it (`quad`, CSS px — the marks do\n// not zoom), one `marks_shade` per covered fragment, premultiplied out; a mark of light returns alpha 0,\n// so the blend (one, one − src alpha) adds it. The glyphs are the rulers' mono atlas (RULER.md), the\n// mat's own texture — the pills' and the rulers' numerals are one face.\n\n@group(0) @binding(0) var<uniform> u: MarksUniforms;\n@group(0) @binding(1) var<storage, read> marks: array<Mark>;\n@group(0) @binding(2) var glyph_tex: texture_2d<f32>;\n@group(0) @binding(3) var glyph_samp: sampler;\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\nconst CORNERS = array<vec2f, 6>(\n vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0),\n vec2f(0.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 1.0),\n);\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VSOut {\n var out: VSOut;\n out.idx = iid;\n let q = marks[iid].quad;\n let pos = mix(q.xy, q.zw, CORNERS[vid]); // CSS px\n out.clip = vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let dpr = max(u.view.z, 1e-6);\n var c = marks_shade(marks[in.idx], in.clip.xy / dpr, dpr, u.atlas, glyph_tex, glyph_samp);\n // the tray's name tags fade in under the drawer's header as its specimens do (design-018 §4, R4) — from the first tag's record on\n if (u.fade.y > 0.0 && f32(in.idx) >= u.fade.z) { c *= smoothstep(u.fade.x, u.fade.x + u.fade.y, in.clip.y / dpr); }\n if (c.a <= 0.0 && max(c.r, max(c.g, c.b)) <= 0.0) { discard; }\n return c;\n}\n";
|
|
6
|
+
readonly "marks/marks.wgsl": "// The MARKS — the desk's chrome as SDF terms (design-015 §7, stratum 5; D4a): *Marks on the Mat*'s\n// reference drawing code (vibe-field/draft/desk-chrome/chrome.js) as distances. Every mark is ONE\n// record (a `Mark`, laid out on the CPU by src/marks/layout.ts in SCREEN px — the chrome keeps its size\n// at every zoom), shaded here into a premultiplied colour: an outline or a fill of a turned rounded\n// box (a frame, a ring, a knob, the vellum, a pill), the four brackets of a frame, a segment (solid or\n// dotted, a laser's line), a gap's bar with its end ticks, a flare, a glyph of the rulers' mono atlas\n// (with its halo), a strip of masking tape. Light is ADDED: a mark flagged so returns alpha 0 over its\n// colour, which the pass's premultiplied blend turns into dst + src — Canvas2D's \"lighter\".\n// Pure: every resource is a parameter (engine/shader.ts's rule).\n\nconst MARK_STROKE: f32 = 0.0;\nconst MARK_FILL: f32 = 1.0;\nconst MARK_BRACKETS: f32 = 2.0;\nconst MARK_SEGMENT: f32 = 3.0;\nconst MARK_BAR: f32 = 4.0;\nconst MARK_FLARE: f32 = 5.0;\nconst MARK_GLYPH: f32 = 6.0;\nconst MARK_TAPE: f32 = 7.0;\n\n/** Coverage of a distance (CSS px) over one DEVICE px of ramp. */\nfn marks_cov(sd: f32, dpr: f32) -> f32 { return clamp(0.5 - sd * dpr, 0.0, 1.0); }\n\n/** A screen point in a mark's own frame: about its centre, turned by −angle (positive turns clockwise on screen, y down). */\nfn marks_local(p: vec2f, c: vec2f, angle: f32) -> vec2f {\n let d = p - c;\n let cs = cos(angle);\n let sn = sin(angle);\n return vec2f(cs * d.x + sn * d.y, -sn * d.x + cs * d.y);\n}\n\n/**\n * The distance to the four BRACKETS of a rounded frame (half extents X, Y, corner r), each reaching L from its\n * corner along both sides (chrome.js `bracketsPath`): folded into one quadrant, the run along the top edge from\n * its end to the arc, the arc, and the run down the side — round caps come from the distance itself.\n */\nfn marks_brackets(p: vec2f, X: f32, Y: f32, r: f32, L: f32) -> f32 {\n let q = abs(p);\n let top = length(q - vec2f(clamp(q.x, X - L, X - r), Y));\n let side = length(q - vec2f(X, clamp(q.y, Y - L, Y - r)));\n var d = min(top, side);\n let v = q - vec2f(X - r, Y - r);\n if (r > 0.0 && v.x >= 0.0 && v.y >= 0.0) { d = min(d, abs(length(v) - r)); }\n return d;\n}\n\n/** The distance to a dotted line A→B: a dot every `period` from A (Canvas2D's `[0.01, period − 0.01]` dash with round caps). */\nfn marks_dotted(p: vec2f, a: vec2f, b: vec2f, period: f32) -> f32 {\n let ab = b - a;\n let len = length(ab);\n let dir = ab / max(len, 1e-6);\n let k = clamp(round(dot(p - a, dir) / period), 0.0, floor(len / period));\n return length(p - (a + dir * (k * period)));\n}\n\n/** A gap's bar: the segment A→B and a tick `tick` either side of it at each end, one stroke. */\nfn marks_bar(p: vec2f, a: vec2f, b: vec2f, tick: f32) -> f32 {\n let ab = b - a;\n let n = vec2f(-ab.y, ab.x) / max(length(ab), 1e-6) * tick;\n return min(sdf_segment(p, a, b), min(sdf_segment(p, a - n, a + n), sdf_segment(p, b - n, b + n)));\n}\n\n/** Masking tape's torn outline (desk.css `.obj-tape i` clip-path), unit square, clockwise from the top-left. */\nconst TAPE_EDGE = array<vec2f, 12>(\n vec2f(0.03, 0.0), vec2f(0.97, 0.04), vec2f(1.0, 0.22), vec2f(0.96, 0.40), vec2f(1.0, 0.60), vec2f(0.97, 0.80),\n vec2f(1.0, 1.0), vec2f(0.02, 0.96), vec2f(0.0, 0.78), vec2f(0.04, 0.58), vec2f(0.0, 0.40), vec2f(0.03, 0.20),\n);\n\n/** The signed distance to the tape's outline, the strip `half` in its own frame (Quilez's polygon). */\nfn marks_tape_sd(p: vec2f, half: vec2f) -> f32 {\n let v0 = (TAPE_EDGE[0] - 0.5) * 2.0 * half;\n var d = dot(p - v0, p - v0);\n var s = 1.0;\n for (var i = 0u; i < 12u; i++) {\n let j = (i + 11u) % 12u;\n let vi = (TAPE_EDGE[i] - 0.5) * 2.0 * half;\n let vj = (TAPE_EDGE[j] - 0.5) * 2.0 * half;\n let e = vj - vi;\n let w = p - vi;\n let b = w - e * clamp(dot(w, e) / dot(e, e), 0.0, 1.0);\n d = min(d, dot(b, b));\n let c0 = p.y >= vi.y;\n let c1 = p.y < vj.y;\n let c2 = e.x * w.y > e.y * w.x;\n if ((c0 && c1 && c2) || (!c0 && !c1 && !c2)) { s = -s; }\n }\n return s * sqrt(d);\n}\n\n/** CSS `saturate(s) brightness(b)` on an sRGB colour (the Filter Effects matrix). */\nfn marks_moonlit(c: vec3f, s: f32, b: f32) -> vec3f {\n let r = vec3f(0.213 + 0.787 * s, 0.715 - 0.715 * s, 0.072 - 0.072 * s);\n let g = vec3f(0.213 - 0.213 * s, 0.715 + 0.285 * s, 0.072 - 0.072 * s);\n let bl = vec3f(0.213 - 0.213 * s, 0.715 - 0.715 * s, 0.072 + 0.928 * s);\n return clamp(vec3f(dot(r, c), dot(g, c), dot(bl, c)) * b, vec3f(0.0), vec3f(1.0));\n}\n\n/** One texel sample of a cell (texels from its top-left, kept inside the cell: a neighbour's glyph never bleeds in). */\nfn marks_glyph_at(t: vec2f, cell: f32, size: vec2f, atlas: vec4f, tex: texture_2d<f32>, samp: sampler) -> f32 {\n let at = vec2f(cell * atlas.w, 0.0) + clamp(t, vec2f(0.5), size - 0.5);\n return textureSampleLevel(tex, samp, at / atlas.xy, 0.0).r;\n}\n\n/** The glyph atlas's coverage at a point of a cell's own frame (CSS px from its top-left; the cell `size` CSS px), `halo` > 0 = its coverage dilated that far. */\nfn marks_glyph_cov(q: vec2f, cell: f32, size: vec2f, atlas: vec4f, halo: f32, tex: texture_2d<f32>, samp: sampler) -> f32 {\n let scale = atlas.z;\n let t = q * scale;\n let s = size * scale;\n var c = marks_glyph_at(t, cell, s, atlas, tex, samp);\n if (halo > 0.0) {\n for (var i = 0; i < 8; i++) {\n let a = f32(i) * 0.785398163;\n let o = vec2f(cos(a), sin(a)) * halo * scale;\n c = max(c, marks_glyph_at(t + o, cell, s, atlas, tex, samp));\n c = max(c, marks_glyph_at(t + o * 0.5, cell, s, atlas, tex, samp));\n }\n }\n return c;\n}\n\n/**\n * One mark at screen point `p` (CSS px), premultiplied — alpha 0 when it is light (added). `atlas` = the glyph atlas's\n * width, height (texels), texels per CSS px and cell width (texels).\n */\nfn marks_shade(M: Mark, p: vec2f, dpr: f32, atlas: vec4f, tex: texture_2d<f32>, samp: sampler) -> vec4f {\n let kind = M.shape.x;\n let width = M.shape.z;\n var cov = 0.0;\n var col = M.colour;\n if (kind == MARK_STROKE || kind == MARK_FILL || kind == MARK_BRACKETS) {\n let q = marks_local(p, M.centre.xy, M.centre.z);\n let sd = sdf_round_box(q, M.half.xy, M.centre.w);\n if (kind == MARK_STROKE) {\n cov = marks_cov(abs(sd) - 0.5 * width, dpr);\n } else if (kind == MARK_FILL) {\n cov = marks_cov(sd, dpr);\n } else {\n cov = marks_cov(marks_brackets(q, M.half.x, M.half.y, M.centre.w, M.shape.w) - 0.5 * width, dpr);\n }\n } else if (kind == MARK_SEGMENT) {\n let d = select(sdf_segment(p, M.centre.xy, M.half.xy), marks_dotted(p, M.centre.xy, M.half.xy, M.aux.x), M.aux.x > 0.0);\n cov = marks_cov(d - 0.5 * width, dpr);\n } else if (kind == MARK_BAR) {\n cov = marks_cov(marks_bar(p, M.centre.xy, M.half.xy, M.aux.x) - 0.5 * width, dpr);\n } else if (kind == MARK_FLARE) {\n // a radial gradient: the core at the centre, the laser at `stop`, nothing at the rim — interpolated premultiplied\n let t = length(p - M.centre.xy) / M.centre.w;\n if (t >= 1.0) { return vec4f(0.0); }\n let core = vec4f(M.aux.rgb * M.aux.a, M.aux.a);\n let mid = vec4f(M.colour.rgb * M.colour.a, M.colour.a);\n let stop = M.half.x;\n let c = select(mix(mid, vec4f(0.0), (t - stop) / (1.0 - stop)), mix(core, mid, t / stop), t < stop);\n return vec4f(c.rgb, select(c.a, 0.0, (u32(M.shape.y) & 1u) != 0u));\n } else if (kind == MARK_GLYPH) {\n // the cell turned about its centre (`centre.xy`), its own frame from its top-left; a halo reaches `aux.y` past it\n let q = marks_local(p, M.centre.xy, M.centre.z) + 0.5 * M.half.xy;\n let h = M.aux.y;\n if (q.x < -h || q.y < -h || q.x > M.half.x + h || q.y > M.half.y + h) { return vec4f(0.0); }\n cov = marks_glyph_cov(q, M.aux.x, M.half.xy, atlas, h, tex, samp);\n } else if (kind == MARK_TAPE) {\n // the strip in its own frame: its torn outline, its fibres (1 in `every` object units), a light from above, moonlit by night\n let q = marks_local(p, M.centre.xy, M.centre.z);\n let half = M.half.xy;\n cov = marks_cov(marks_tape_sd(q, half), dpr);\n let uv = (q + half) / (2.0 * half);\n let units = M.aux.x; // CSS px per object unit\n let sheen = mix(vec4f(1.0, 1.0, 1.0, M.half.z), vec4f(0.0, 0.0, 0.0, M.half.w), clamp(uv.y, 0.0, 1.0));\n var c = vec4f(M.colour.rgb * M.colour.a, M.colour.a);\n c = vec4f(sheen.rgb * sheen.a, sheen.a) + c * (1.0 - sheen.a);\n let every = M.shape.w;\n let x = (q.x + half.x) / max(units, 1e-6);\n let seen = clamp(every * units * dpr / 3.0 - 0.5, 0.0, 1.0); // fibres under ~1.5 device px each fade to the mean\n let fibre = select(0.0, 1.0, fract(x / every) < 1.0 / every) * seen + (1.0 - seen) / every;\n let fa = M.aux.w * fibre;\n c = vec4f(vec3f(fa), fa) + c * (1.0 - fa);\n let rgb = marks_moonlit(c.rgb / max(c.a, 1e-6), M.aux.y, M.aux.z);\n let a = c.a * cov * M.centre.w;\n return vec4f(rgb * a, a);\n }\n let a = col.a * cov;\n return vec4f(col.rgb * a, select(a, 0.0, (u32(M.shape.y) & 1u) != 0u));\n}\n";
|
|
7
|
+
readonly "mat/mat-pass.wgsl": "// PASS 2, the cutting mat — one fullscreen triangle: the lattice's lines as\n// analytic coverage, the grain, the gobo projected onto the desk plane, the\n// reference's grade by day or the night's eye by night (MAT.md). Alpha 1 at\n// rest (a plain write); a flight fades it and clips it to the portal.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var gobo_tex: texture_2d<f32>;\n@group(0) @binding(2) var gobo_samp: sampler;\n@group(0) @binding(3) var noise_tex: texture_2d<f32>;\n@group(0) @binding(4) var noise_samp: sampler;\n@group(0) @binding(5) var glyph_tex: texture_2d<f32>; // the rulers' glyph atlas (RULER.md); a 1×1 empty cell until a host uploads one\n\nstruct VSOut { @builtin(position) clip: vec4f }\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n var out: VSOut;\n out.clip = vec4f(p[vid], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let zoom = mat_zoom(u);\n let dpr = mat_dpr(u);\n let frag = in.clip.xy; // device px\n let screen = frag / dpr; // CSS px\n let px = 1.0 / (zoom * dpr); // world per device px\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n // the rulers' print — screen-space ink mixed into the albedo before the light, so it is lit and shadowed like the lines\n let ruler = ruler_ink(u, screen, frag, glyph_tex, gobo_samp);\n let albedo = mat_albedo(u, u.phase.xy + screen / zoom, px, frag, bn, ruler);\n // the lamp's gobo where the LIGHT sees this pixel (MINIMAT.md §4): the slot's own camera at rest\n // (`light` = `cam`, so this is the desk under the pixel), the host desk's through a mini mat's face\n var gobo = sample_gobo(u, gobo_tex, gobo_samp, desk_of(u, u.light.xy + screen / max(u.light.z, 1e-12)), bn.z);\n if (u.light.w > 0.0) { gobo = mix(gobo, sample_gobo(u, gobo_tex, gobo_samp, desk_of(u, u.light2.xy + screen / max(u.light2.z, 1e-12)), bn.z), u.light.w); }\n // Straight alpha: the presentation's opacity through the portal clip — both 1 at rest,\n // when \"source over\" reduces to a plain write.\n return vec4f(mat_colour(u, albedo, gobo, bn.y), u.presence.x * portal_cover(screen, u.portals, u.clips, dpr));\n}\n";
|
|
8
|
+
readonly "mat/mat.wgsl": "// The cutting mat's maths — a PURE module: no bindings. The uniform block and\n// the textures arrive as parameters. Every function body is the reference's\n// (research/tree-shadow/prototype/src/shaders.js), transcribed to the engine's\n// units: world = CSS px at zoom 1, the lattice's decade rungs, `MatUniforms`\n// from src/mat/layout.ts prepended by the engine.\n\nfn mat_zoom(u: MatUniforms) -> f32 { return max(u.cam.z, 1e-12); }\nfn mat_dpr(u: MatUniforms) -> f32 { return max(u.cam.w, 1.0); }\n\n// ---- the reference's noise (hash12 · valueNoise), on framebuffer pixels\nfn hash12(p: vec2f) -> f32 {\n let n = sin(dot(p, vec2f(127.1, 311.7))) * 43758.5453;\n return n - floor(n);\n}\n\nfn value_noise(p: vec2f) -> f32 {\n let i = floor(p);\n var f = fract(p);\n f = f * f * (3.0 - 2.0 * f);\n return mix(\n mix(hash12(i), hash12(i + vec2f(1.0, 0.0)), f.x),\n mix(hash12(i + vec2f(0.0, 1.0)), hash12(i + vec2f(1.0, 1.0)), f.x),\n f.y);\n}\n\nfn srgb_to_linear(c: vec3f) -> vec3f {\n return mix(c / 12.92, pow((c + 0.055) / 1.055, vec3f(2.4)), step(vec3f(0.04045), c));\n}\n\nfn rgb2hsb(c: vec3f) -> vec3f {\n let K = vec4f(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n let p = mix(vec4f(c.bg, K.wz), vec4f(c.gb, K.xy), step(c.b, c.g));\n let q = mix(vec4f(p.xyw, c.r), vec4f(c.r, p.yzx), step(p.x, c.r));\n let d = q.x - min(q.w, q.y);\n let e = 1.0e-10;\n return vec3f(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n}\n\nfn hsb2rgb(c: vec3f) -> vec3f {\n let K = vec4f(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n let p = abs(fract(K.xyz + c.x) * 6.0 - K.w);\n return c.z * mix(K.xxx, clamp(p - K.x, vec3f(0.0), vec3f(1.0)), c.y);\n}\n\n// ---- the lines. `px` is world units per DEVICE pixel; half-widths are in device px.\nfn line_coverage(x: f32, spacing: f32, half_width: f32, px: f32) -> f32 {\n let d = abs(fract(x / spacing - 0.5) - 0.5) * spacing;\n return 1.0 - smoothstep(max(half_width - px, 0.0), half_width + px, d);\n}\n\nfn grid2(p: vec2f, spacing: f32, half_width: f32, px: f32) -> f32 {\n return max(line_coverage(p.x, spacing, half_width, px), line_coverage(p.y, spacing, half_width, px));\n}\n\n// One law for every rung (src/lattice/line.ts `lineWeight`): fade in over the\n// engine's window by the rung's OWN cell, then thicken and darken over the\n// next decade. x = half-width (device px), y = alpha.\nfn line_weight(u: MatUniforms, cell_px: f32) -> vec2f {\n let s = smoothstep(u.lod.x, u.lod.y, cell_px);\n let t = saturate(log(max(cell_px, 1e-9) / u.lod.y) / 2.302585093);\n return vec2f(mix(u.line.x, u.line.y, t), u.line.z * s + (u.line.w - u.line.z) * t);\n}\n\n// The mat's albedo at a point: the reference's `cuttingMatAlbedo` with the\n// three lattice rungs in place of its own decade, and the line law above in\n// place of its fade ladder. `world` is lattice-phase world (wrapped); `px` is\n// world per device pixel; `frag` is the framebuffer pixel; `ruler` the rulers'\n// print at it (ruler.wgsl) — ink over the lines, and at 0 exactly the mix below\n// returns the lined mat untouched, bit for bit.\nfn mat_albedo(u: MatUniforms, world: vec2f, px: f32, frag: vec2f, bn: vec3f, ruler: f32) -> vec3f {\n var sage = u.ground.xyz;\n let amp = u.grain.x;\n var g = (hash12(frag * vec2f(0.73, 0.91)) - 0.5) * amp;\n g += (value_noise(frag * 0.45 + vec2f(u.view.z * 0.9, -u.view.z * 0.6)) - 0.5) * amp;\n g += (bn.x - 0.5) * amp * 0.5;\n sage += vec3f(g * 0.4, g, g * 0.35);\n\n // A rung under the window has alpha 0 and no coverage to compute — the\n // fine rung at most zooms. The branches are uniform across the frame.\n let zoom = mat_zoom(u);\n var cover = 0.0;\n let wf = line_weight(u, u.rungs.x * zoom);\n if (wf.y > 0.0) { cover = grid2(world, u.rungs.x, wf.x * px, px) * wf.y; }\n let wm = line_weight(u, u.rungs.y * zoom);\n if (wm.y > 0.0) { cover = max(cover, grid2(world, u.rungs.y, wm.x * px, px) * wm.y); }\n let wc = line_weight(u, u.rungs.z * zoom);\n if (wc.y > 0.0) { cover = max(cover, grid2(world, u.rungs.z, wc.x * px, px) * wc.y); }\n return srgb_to_linear(clamp(mix(mix(sage, u.ink.xyz, cover), u.ink.xyz, ruler), vec3f(0.0), vec3f(1.0)));\n}\n\n// ---- the gobo (the reference's sampleBlur · getGoboBlurRatio · sampleGobo)\nfn linearstep(edge0: f32, edge1: f32, x: f32) -> f32 {\n return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);\n}\n\n// Linearise the WebGL-range NDC z with the projector's near/far, then ramp\n// between the two depths: sharp near the projector, soft far from it.\nfn blur_ratio(ndc_z: f32, p: vec4f) -> f32 {\n return linearstep(p.x, p.y, (2.0 * p.z * p.w) / (p.w + p.z - ndc_z * (p.w - p.z)));\n}\n\n// Six taps on a golden-angle spiral, the start angle from blue noise so the\n// pattern differs per pixel (and per frame while the offset moves).\nfn sample_blur(tex: texture_2d<f32>, samp: sampler, uv: vec2f, texel: vec2f, blur: f32, n: f32) -> f32 {\n // No blur: six taps at one point are one tap. The sharp end of the desk.\n if (blur <= 0.0) { return textureSampleLevel(tex, samp, uv, 0.0).r; }\n let G = 2.39996323;\n let rot = mat2x2f(cos(G), sin(G), -sin(G), cos(G));\n let a0 = n * 6.28318530718;\n var d = vec2f(cos(a0), sin(a0));\n let s = texel * blur * (1.0 / sqrt(6.0));\n var c = 0.0;\n for (var i = 0; i < 6; i++) {\n c += textureSampleLevel(tex, samp, uv + d * sqrt(f32(i + 1)) * s, 0.0).r;\n d = rot * d;\n }\n return c / 6.0;\n}\n\n// The gobo term at a desk point (metres): 1 = lit. Outside the projector's\n// frame the plate is not consulted at all — the cone is finite, as the\n// reference's is.\nfn sample_gobo(u: MatUniforms, tex: texture_2d<f32>, samp: sampler, desk: vec3f, n: f32) -> f32 {\n if (u.gobo.x <= 0.0) { return 1.0; }\n var clip = u.goboMatrix * vec4f(desk, 1.0);\n let ndc = clip.xyz / clip.w;\n // The wind target's row 0 is the plate's top row, so v runs downward.\n let uv = vec2f(0.5 + 0.5 * ndc.x, 0.5 - 0.5 * ndc.y);\n if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 1.0; }\n let g = sample_blur(tex, samp, uv, vec2f(1.0 / 512.0), blur_ratio(ndc.z, u.goboParams) * u.gobo.y, n);\n return mix(1.0, g, u.gobo.x);\n}\n\n// World (CSS px at zoom 1, unwrapped) → the desk plane in the projector's frame.\nfn desk_of(u: MatUniforms, world: vec2f) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w, u.plane.y + world.y * u.plane.z);\n}\n\n// ---- the LIGHT a slot is lit by (MINIMAT.md §4; src/mat/layout.ts `SlotLight`). The lamp's gobo is\n// sampled where a light CAMERA sees a point, not where the slot's camera does: the same camera for the\n// root at rest; the host desk's for a mini mat's inside — the dapple runs across the mini mat as across\n// anything lying on the desk — and two of them cross-faded (`light.w`) while a flight hands one lamp to\n// the other. An object's world point `p` raised `h` world units, as light camera `L` (x, y, zoom) sees it:\n// the point on screen, back into L's world; a height scales as a length does.\nfn lit_desk(u: MatUniforms, L: vec4f, p: vec2f, h: f32) -> vec3f {\n let s = max(u.cam.z, 1e-12) / max(L.z, 1e-12);\n let w = L.xy + (p - u.cam.xy) * s;\n return vec3f(u.plane.x + w.x * u.plane.z, u.plane.w + h * s * u.plane.z, u.plane.y + w.y * u.plane.z);\n}\n\n// The gobo on an object's world point `p` raised `h`: under the slot's own lamp the desk point itself,\n// `own` (the object's own `desk_at`, so a root's object reads exactly what it always read); else under\n// the light camera — then, while a flight hands it over, cross-faded to the second.\nfn lit_gobo(u: MatUniforms, tex: texture_2d<f32>, samp: sampler, p: vec2f, h: f32, own: vec3f, n: f32) -> f32 {\n var g = 0.0;\n if (all(u.light.xyz == u.cam.xyz)) { g = sample_gobo(u, tex, samp, own, n); }\n else { g = sample_gobo(u, tex, samp, lit_desk(u, u.light, p, h), n); }\n if (u.light.w > 0.0) { g = mix(g, sample_gobo(u, tex, samp, lit_desk(u, u.light2, p, h), n), u.light.w); }\n return g;\n}\n\n// The reference's grade: `shade = mix(1, floor + (1 − floor)·gobo, mix)`, then\n// HSB: saturation up by (1 − shade)·k, brightness × shade. The reference did\n// this on pow(albedo, 2.2) and wrote pow(1/2.2) — kept verbatim: that chain is\n// the mat's colour on screen.\nfn shade_mat(u: MatUniforms, albedo: vec3f, gobo: f32) -> vec3f {\n // Lit: shade is exactly 1, the saturation bump exactly 0, and the chain is\n // pow(pow(albedo, 2.2), 1/2.2) — the identity. Every pixel outside the\n // cone, and every lit one inside it, skips six pows and the HSB round trip.\n if (gobo >= 1.0) { return albedo; }\n let shade = mix(1.0, u.gobo.w + (1.0 - u.gobo.w) * gobo, u.gobo.z);\n var hsb = rgb2hsb(pow(albedo, vec3f(2.2)));\n hsb.y = min(hsb.y + (1.0 - shade) * u.grade.x, 1.0);\n hsb.z = hsb.z * shade;\n return pow(hsb2rgb(hsb), vec3f(1.0 / 2.2));\n}\n\n// ---- the NIGHT (MAT.md; src/mat/night.ts is the same arithmetic on the CPU):\n// the Moon in place of the Sun, and the eye that sees by it. The day's chain\n// above is untouched — `mat_colour` returns it, alone, when `night.x` is 0.\nfn night_luminance(c: vec3f) -> f32 { return dot(c, vec3f(0.2126729, 0.7151522, 0.0721750)); }\n\n// Scotopic luminance V' from XYZ — Larson, Rushmeier & Piatko 1997's regression:\n// the rods peak at 507 nm, so greens and blues keep their brightness by night and reds die.\nfn night_scotopic(c: vec3f) -> f32 {\n let X = dot(c, vec3f(0.4124564, 0.3575761, 0.1804375));\n let Y = dot(c, vec3f(0.2126729, 0.7151522, 0.0721750));\n let Z = dot(c, vec3f(0.0193339, 0.1191920, 0.9503041));\n return Y * (1.33 * (1.0 + (Y + Z) / max(X, 1e-6)) - 1.68);\n}\n\n// CIE 191:2010's mesopic weight — the cones' share of the seeing at L cd/m²:\n// 0 at 0.005 (the rods alone), 1 at 5 (the cones alone).\nfn night_mesopic(L: f32) -> f32 { return saturate(0.767 + 0.3334 * log(max(L, 1e-9)) * 0.4342944819); }\n\nfn night_encode(c: vec3f) -> vec3f {\n return mix(c * 12.92, 1.055 * pow(max(c, vec3f(0.0)), vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), c));\n}\n\nfn night_mat(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n let shade = u.moon.w + (1.0 - u.moon.w) * gobo; // the Moon, and the sky's share where it is blocked\n let cone = albedo * u.moon.xyz * shade; // the mat under the Moon, as the partly adapted cones take it\n let rod = max(night_scotopic(cone), 0.0) / night_scotopic(vec3f(1.0)); // the rods' signal, white = 1\n let L = u.night.y * night_luminance(cone) * 0.3183098862; // cd/m² on the desk: E · ρ / π\n let see = mix(rod * u.rod.xyz, cone, night_mesopic(L)); // what the eye reports\n let disp = u.eigengrau.xyz + see * u.night.z; // the dark's own grey under it, at the appearance's scale\n return night_encode(disp) + (noise - 0.5) * u.night.w; // encoded; then the rods' own noise\n}\n\n// The mat's colour on screen: the day's chain, the night, or a cross-fade of the two.\nfn mat_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n if (u.night.x <= 0.0) { return shade_mat(u, albedo, gobo); }\n let night = night_mat(u, albedo, gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(shade_mat(u, albedo, gobo), night, u.night.x);\n}\n";
|
|
9
|
+
readonly "mat/ruler.wgsl": "// The RULERS printed on the mat (RULER.md) — a PURE module: no bindings. The\n// ink coverage at a fragment from the frame's lines, the ticks and the labels,\n// in screen space; the mat pass mixes it into the albedo before the light, so\n// the print is lit and shadowed like the lines. Every number is a uniform\n// `matUniformValues` fills from lattice/ruler.ts — the levels' spacings,\n// presences and tick lengths per frame; the shader only lays them out.\n//\n// Layout: the frame's outer rect one `margin` in from the viewport; the top\n// and left BANDS one `band` wide inside it, each closed by its inner line\n// (the ticks' baseline). A tick stands on the baseline at every site of every\n// level, its length the level's, its presence the level's. A label sits in\n// the band's outer half after its tick, laid out on the finest labelled\n// level's sites; the left ruler's labels are the top's rotated a quarter turn\n// clockwise (reading down the band, their tops toward the field) — James's\n// mockup. All distances are DEVICE px inside; positions arrive as CSS px.\n\nconst RULER_LEVELS = 5;\nconst GLYPH_PAD = 2.0;\n\n// A printed line's coverage across `d` device px at half-width `hw`: a 1 px AA ramp.\nfn ruler_line(d: f32, hw: f32) -> f32 { return 1.0 - smoothstep(hw - 0.5, hw + 0.5, abs(d)); }\n// Inside [a, b] — the ends of a segment and the extents of a band.\nfn ruler_in(x: f32, a: f32, b: f32) -> f32 { return step(a, x) * step(x, b); }\n\nfn ruler_digits(a: i32) -> i32 { var n = 1; var v = a; loop { if (v < 10) { break; } v = v / 10; n += 1; } return n; }\nfn ruler_pow10(p: i32) -> i32 { var r = 1; for (var i = 0; i < p; i++) { r = r * 10; } return r; }\n// The i-th digit (0 = leftmost) of `a` written with `nd` digits — leading zeros where `a` is short.\nfn ruler_digit_at(a: i32, i: i32, nd: i32) -> i32 { return (a / ruler_pow10(nd - 1 - i)) % 10; }\n\n// A label's text — lattice/ruler.ts `formatLabel`, per character: the glyph\n// index at slot `k` of `index · mult · 10^exp` (0–9, 10 '-', 11 '.', -1 past\n// the end), and (x < 0) the character count. Plain digits, no suffix; a\n// fraction with its trailing zeros stripped, so \"1.0\" is \"1\" at every level.\nfn ruler_label(index: i32, mult: i32, exp: i32, k: i32) -> i32 {\n let M = index * mult;\n if (M == 0) { return select(-1, 0, k == 0); }\n let neg = M < 0;\n let a = abs(M);\n var i = k;\n if (neg) { if (i == 0) { return 10; } i -= 1; }\n if (exp >= 0) {\n let nd = ruler_digits(a);\n if (i < nd) { return ruler_digit_at(a, i, nd); }\n if (i < nd + exp) { return 0; }\n return -1;\n }\n let p = -exp;\n let nd = max(ruler_digits(a), p + 1);\n let intDigits = nd - p;\n var fracLen = p;\n loop { if (fracLen == 0) { break; } if ((a / ruler_pow10(p - fracLen)) % 10 != 0) { break; } fracLen -= 1; }\n if (i < intDigits) { return ruler_digit_at(a, i, nd); }\n if (i == intDigits) { return select(-1, 11, fracLen > 0); }\n let j = i - intDigits - 1;\n if (j < fracLen) { return ruler_digit_at(a, intDigits + j, nd); }\n return -1;\n}\nfn ruler_label_len(index: i32, mult: i32, exp: i32) -> i32 {\n var n = 0;\n loop { if (n >= 16) { break; } if (ruler_label(index, mult, exp, n) < 0) { break; } n += 1; }\n return n;\n}\n\n// The ticks and the label along ONE band. `along` is the fragment's CSS px\n// along the band (x for the top, y for the left), `across` its device px in\n// from the outer line; `phase` the lattice's wrapped camera coordinate along\n// it, `wraps` the wrap count, `lim` the band's extent along (CSS px: the inner\n// corner, the far frame line), `turned` the left band. `glyph` receives the\n// atlas texel to sample when the fragment lies in a label (its .z the label's\n// presence), else .z 0.\nstruct RulerHit { ink: f32, glyph: vec3f }\n\nfn ruler_band(u: MatUniforms, along: f32, across: f32, phase: f32, wraps: f32, lim: vec2f, turned: bool) -> RulerHit {\n var hit: RulerHit;\n hit.ink = 0.0; hit.glyph = vec3f(0.0);\n let dpr = mat_dpr(u); let zoom = mat_zoom(u);\n let band_dev = u.ruler.z * dpr;\n let hw = 0.5 * u.ruler.w * dpr;\n // only the band's own pixels pay for the levels and the text — the rest of the mat returns here\n if (across < -1.0 || across > band_dev + 1.0 || ruler_in(along, lim.x, lim.y) <= 0.0) { return hit; }\n let w = phase + along / zoom; // lattice-phase world along the band\n // the ticks: on the baseline (the band's inner edge), up the level's length; the coarsest level owning a site wins the max\n var tick = 0.0;\n for (var i = 0; i < RULER_LEVELS; i++) {\n let s = u.rulerSites[i].x;\n let len = u.rulerSites[i].y * dpr;\n let a = u.rulerSites[i].z;\n if (a <= 0.0 || len <= 0.0) { continue; }\n let d = (abs(fract(w / s - 0.5) - 0.5) * s) * zoom * dpr;\n tick = max(tick, ruler_line(d, hw) * ruler_in(across, band_dev - len - 0.5, band_dev + 0.5) * a);\n }\n hit.ink = tick * u.rulerAlpha.y;\n // the label: laid out on the finest labelled level's sites, the site at or before the fragment\n let lf = i32(u.rulerOrigin.z);\n if (lf < 0 || u.rulerAtlas.w <= 0.0) { return hit; }\n let s = u.rulerSites[lf].x;\n let nLocal = floor(w / s);\n let site = (nLocal * s - phase) * zoom; // CSS px along\n if (site < lim.x) { return hit; } // a site inside the corner square prints nothing\n let index = i32(wraps) * i32(u.rulerFormat[lf].z) + i32(nLocal);\n // the site's presence: the coarsest level it lies on\n var presence = u.rulerSites[lf].w;\n for (var j = RULER_LEVELS - 1; j > lf; j--) {\n let ratio = i32(u.rulerFormat[lf].z) / i32(u.rulerFormat[j].z);\n if (index % ratio == 0) { presence = u.rulerSites[j].w; break; }\n }\n if (presence <= 0.0) { return hit; }\n let mult = i32(u.rulerFormat[lf].x); let exp = i32(u.rulerFormat[lf].y);\n let texel = u.rulerText.w / dpr; // atlas texels per device px\n let adv = u.rulerGlyph.z / texel; // a glyph's advance, device px\n let origin = floor((site + u.rulerText.y) * dpr + 0.5); // the text's start, on a whole device px\n let t = along * dpr - origin;\n if (t < 0.0) { return hit; }\n let slot = i32(floor(t / adv));\n let g = ruler_label(index, mult, exp, slot);\n if (g < 0) { return hit; }\n // the glyph cell. The top band: the cap's top `top` in from the outer line, the glyph read down the band. The left band: the\n // glyph turned a quarter CLOCKWISE — read down the band still, but its baseline `top` in from the outer line and its top toward\n // the field (the mockup's \"1\" above \"0\", the foot of the \"1\" at the outer side). Reading along +y with the cell's rows along −x\n // keeps the hand: a rotation, not the mirror a transpose would be.\n var v = 0.0;\n if (turned) { v = u.rulerGlyph.w - (across - u.rulerText.x * dpr) * texel; }\n else { v = (across - (u.rulerText.x * dpr - (u.rulerGlyph.w - u.rulerAtlas.z) / texel)) * texel; }\n if (v < 0.0 || v >= u.rulerGlyph.y) { return hit; }\n let uu = f32(g) * u.rulerGlyph.x + GLYPH_PAD + (t - f32(slot) * adv) * texel;\n hit.glyph = vec3f(uu, v, presence);\n return hit;\n}\n\n// The whole print: the frame's lines, the two bands' ticks, the labels'\n// texels sampled from the atlas. Returns ink coverage 0..1 at this fragment.\nfn ruler_ink(u: MatUniforms, screen: vec2f, frag: vec2f, glyph_tex: texture_2d<f32>, samp: sampler) -> f32 {\n if (u.ruler.x <= 0.0) { return 0.0; }\n let dpr = mat_dpr(u);\n let margin = u.ruler.y; let band = u.ruler.z;\n let W = u.view.x; let H = u.view.y;\n let hw = 0.5 * u.ruler.w * dpr;\n let m = margin * dpr; let inner = (margin + band) * dpr;\n let right = (W - margin) * dpr; let bottom = (H - margin) * dpr;\n // the frame: the outer rect, and the two baselines closing the bands\n var frame = 0.0;\n frame = max(frame, ruler_line(frag.x - m, hw) * ruler_in(frag.y, m - hw, bottom + hw));\n frame = max(frame, ruler_line(frag.x - right, hw) * ruler_in(frag.y, m - hw, bottom + hw));\n frame = max(frame, ruler_line(frag.y - m, hw) * ruler_in(frag.x, m - hw, right + hw));\n frame = max(frame, ruler_line(frag.y - bottom, hw) * ruler_in(frag.x, m - hw, right + hw));\n frame = max(frame, ruler_line(frag.x - inner, hw) * ruler_in(frag.y, m, bottom));\n frame = max(frame, ruler_line(frag.y - inner, hw) * ruler_in(frag.x, m, right));\n var ink = frame * u.rulerAlpha.x;\n // the top band (across = down from the outer line) and the left band (across = right from it); a pixel in neither pays nothing more.\n // The labels: the top band's cell read along x, the left band's along y and turned — the same atlas texels either way.\n if (ruler_in(frag.y, m - 1.0, inner + 1.0) * ruler_in(frag.x, inner - 1.0, right + 1.0) > 0.0) {\n let top = ruler_band(u, screen.x, frag.y - m, u.phase.x, u.rulerOrigin.x, vec2f(margin + band, W - margin), false);\n ink = max(ink, top.ink);\n if (top.glyph.z > 0.0) { ink = max(ink, textureSampleLevel(glyph_tex, samp, top.glyph.xy / u.rulerAtlas.xy, 0.0).r * top.glyph.z * u.rulerAlpha.z); }\n }\n if (ruler_in(frag.x, m - 1.0, inner + 1.0) * ruler_in(frag.y, inner - 1.0, bottom + 1.0) > 0.0) {\n let left = ruler_band(u, screen.y, frag.x - m, u.phase.y, u.rulerOrigin.y, vec2f(margin + band, H - margin), true);\n ink = max(ink, left.ink);\n if (left.glyph.z > 0.0) { ink = max(ink, textureSampleLevel(glyph_tex, samp, left.glyph.xy / u.rulerAtlas.xy, 0.0).r * left.glyph.z * u.rulerAlpha.z); }\n }\n return ink;\n}\n";
|
|
10
|
+
readonly "mat/wind.wgsl": "// The wind pass — the reference's `goboWind`: one fragment per plate texel,\n// R silhouette + three sines whose amplitudes and phase the plate's other\n// channels carry, written to an r8 target the mat pass samples. Runs only when\n// the gobo time moved; the target is 512² like the plate.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var plate: texture_2d<f32>;\n\nstruct VSOut { @builtin(position) clip: vec4f }\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32) -> VSOut {\n var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));\n var out: VSOut;\n out.clip = vec4f(p[vid], 0.0, 1.0);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let params = textureLoad(plate, vec2i(in.clip.xy), 0);\n let time = u.wind.x;\n let strength = u.wind.y;\n let s = params.r;\n let amp1 = params.g * strength;\n let phase1 = (params.b - 0.5) * 6.283185;\n let amp23 = params.a * strength;\n let waves = sin(time * vec3f(1.0, 2.0, 3.0) + phase1 * vec3f(1.0, 2.0, 0.5));\n let motion = (amp1 * waves.x) + (amp23 * 0.5 * waves.y) + (amp23 * 0.3 * waves.z);\n let v = clamp(s + motion, 0.0, 1.0);\n return vec4f(v, 0.0, 0.0, 1.0);\n}\n";
|
|
11
|
+
readonly "portal.wgsl": "// The portal clip — a slot's ground and frames are visible only through a\n// rounded rect on screen (CSS px): its FACE and, for a nested slot, every face\n// above it — the CHAIN (src/nav/portal.ts `Presentation`, `within`). A PURE\n// module every pass includes: the records arrive as parameters. `clips[i].y`\n// < 0.5 ends the chain (an empty chain is no clip at all, and the loop is one\n// compare); `clips[i].x` is a face's corner radius; `clips[i].z` its top\n// FEATHER (CSS px; 0 = a hard edge — design-018 §4, the tray's face: what it\n// shows fades in over that band below its top edge, with no code of its own).\n\n// = nav/portal.ts PORTAL_CHAIN, the uniform arrays' length; a mismatch fails to compile.\nconst PORTAL_CHAIN = 6u;\n\nfn portal_sd(p: vec2f, half: vec2f, r: f32) -> f32 {\n let rr = min(r, min(half.x, half.y));\n let q = abs(p) - half + vec2f(rr, rr);\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rr;\n}\n\n// Coverage at a CSS-px point through ONE face grown by `grow` device px: 1 inside, 0 outside, a one-device-px ramp across the edge —\n// times, where the face has a feather, a smoothstep from 0 at its top edge to 1 the feather below it.\nfn portal_cover_one(p_css: vec2f, portal: vec4f, clip: vec4f, dpr: f32, grow: f32) -> f32 {\n let px = 1.0 / max(dpr, 1.0);\n let d = portal_sd(p_css - portal.xy, portal.zw, clip.x) - grow * px;\n let c = clamp(0.5 - d / px, 0.0, 1.0);\n if (clip.z <= 0.0) { return c; }\n let top = portal.y - portal.w;\n return c * smoothstep(top, top + clip.z, p_css.y);\n}\n\n// Coverage through the chain: the slot's own face grown by `grow` device px (the fill's one\n// px, so the hole's edge partitions with the plate — PORTAL.md §10) times every face above\n// it as it is. The product: a nested slot shows only where all of them agree.\nfn portal_cover_grown(p_css: vec2f, portals: array<vec4f, PORTAL_CHAIN>, clips: array<vec4f, PORTAL_CHAIN>, dpr: f32, grow: f32) -> f32 {\n var c = 1.0;\n for (var i = 0u; i < PORTAL_CHAIN; i++) {\n if (clips[i].y < 0.5) { break; }\n c *= portal_cover_one(p_css, portals[i], clips[i], dpr, select(0.0, grow, i == 0u));\n }\n return c;\n}\n\nfn portal_cover(p_css: vec2f, portals: array<vec4f, PORTAL_CHAIN>, clips: array<vec4f, PORTAL_CHAIN>, dpr: f32) -> f32 {\n return portal_cover_grown(p_css, portals, clips, dpr, 0.0);\n}\n";
|
|
12
|
+
readonly "primitives.wgsl": "// 2D primitives for the desk's objects — the note, the whiteboard, the photo,\n// the mini mat. Pure functions, exact Euclidean distances — which is what\n// everything downstream (1-px AA, the analytic shadows, the hit-test mirrors,\n// src/sdf.ts) relies on. Prefixed `sdf_` so this module can share a program\n// with the mat's module without a name clash. Moved out of shaders/card/ when\n// the card frame retired (2026-09-25).\n// (Lineage: research/sdf-card/src/sdf/primitives.wgsl.)\n\nfn sdf_box(p: vec2f, b: vec2f) -> f32 {\n let q = abs(p) - b;\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0);\n}\n\nfn sdf_round_box(p: vec2f, b: vec2f, r: f32) -> f32 {\n let rr = clamp(r, 0.0, min(b.x, b.y));\n let q = abs(p) - b + rr;\n return length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rr;\n}\n\nfn sdf_circle(p: vec2f, r: f32) -> f32 { return length(p) - r; }\n\nfn sdf_segment(p: vec2f, a: vec2f, b: vec2f) -> f32 {\n let pa = p - a;\n let ba = b - a;\n let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);\n return length(pa - ba * h);\n}\n\nfn sdf_rounded_x(p: vec2f, w: f32, r: f32) -> f32 {\n let q = abs(p);\n return length(q - min(q.x + q.y, w) * 0.5) - r;\n}\n\n// Exact circular fillet CSG (Quilez). Unlike smin/smax these put a TRUE circle\n// of radius r on the seam. Exact when the operands are perpendicular\n// half-planes (the retired card frame's corner was built to satisfy it).\n//\n// The union form clamps its operands to >= -r so the fillet stays local: the\n// unclamped hg_sdf form keeps growing once both surfaces are more than r\n// inside, and the field goes badly wrong in the interior.\nfn op_union_round(a: f32, b: f32, r: f32) -> f32 {\n let m = min(a, b);\n let A = max(a, -r);\n let B = max(b, -r);\n let u = max(vec2f(r - A, r - B), vec2f(0.0));\n return min(m, max(r, min(A, B)) - length(u));\n}\n\nfn op_isect_round(a: f32, b: f32, r: f32) -> f32 {\n let u = max(vec2f(r + a, r + b), vec2f(0.0));\n return min(-r, max(a, b)) + length(u);\n}\n";
|
|
13
|
+
readonly "tray/tray-pass.wgsl": "// The pegboard tray's pass (design-017 §5; K5a; design-018 §2): drawn in the ROOT's render pass after the marks, premultiplied, two kinds\n// of quad — one vertex entry that lays each by its instance, two fragment entries (two pipelines on one layout: a branch over both in one\n// entry cost the board a third of its time, the register budget of the largest path paid by every pixel): instance 0 the drawer UNDER\n// its specimens, its edge included (`fs` → tray.wgsl `tray_drawer` — the view whole while the slide shows the dim, the drawer's box grown\n// by its shadows' reach while nothing dims; shut, nothing is drawn at all); from instance 1, one quad per specimen's ACCESSORY with its\n// shadow (`fs_accessory`, the records in `acc`). The rim's strips over it all retired with the rim (design-018 §2).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> t: TrayUniforms;\n@group(0) @binding(2) var noise_tex: texture_2d<f32>;\n@group(0) @binding(3) var noise_samp: sampler;\n@group(0) @binding(4) var hash_tex: texture_2d<f32>;\n@group(0) @binding(5) var<storage, read> acc: array<TrayAccessory>;\n\nstruct TrayOut {\n @builtin(position) pos: vec4f,\n @location(0) @interpolate(flat) mode: u32,\n @location(1) @interpolate(flat) item: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> TrayOut {\n var corners = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0));\n let k = corners[vi % 6u];\n let vw = t.view.x;\n let vh = t.view.y;\n var lo = vec2f(0.0, 0.0);\n var hi = vec2f(vw, vh);\n var o: TrayOut;\n o.mode = 0u;\n o.item = 0u;\n if (ii == 0u) {\n // dimming: the view whole; undimmed: the drawer's box from its shadows' reach above and beside it down to the view's bottom edge\n if (t.dim <= 0.0) {\n let reach = 3.0 * max(t.shadow.x, t.room.x) + length(t.shadow.zw);\n lo = vec2f(max(t.rect.x - reach, 0.0), max(t.rect.y - reach, 0.0));\n hi = vec2f(min(t.rect.x + t.rect.z + reach, vw), vh);\n }\n } else {\n o.mode = 1u;\n o.item = ii - 1u;\n let b = acc[o.item].box;\n lo = clamp(b.xy, vec2f(0.0), vec2f(vw, vh));\n hi = clamp(b.zw, vec2f(0.0), vec2f(vw, vh));\n }\n let css = mix(lo, hi, k);\n o.pos = vec4f(css.x / vw * 2.0 - 1.0, 1.0 - css.y / vh * 2.0, 0.0, 1.0);\n return o;\n}\n\n@fragment\nfn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n return tray_drawer(u, t, pos.xy, noise_tex, noise_samp, hash_tex);\n}\n\n@fragment\nfn fs_accessory(@builtin(position) pos: vec4f, @location(1) @interpolate(flat) item: u32) -> @location(0) vec4f {\n return tray_accessory(u, t, acc[item], pos.xy, noise_tex, noise_samp, hash_tex);\n}\n\n// THE VEIL (design-018 rev 5): one quad over the drawer's top — from its edge down through the header and its ramp — laid LAST, over\n// everything the drawer holds (tray.wgsl `tray_veil`: the plain board, whole in the header, fading out over the ramp).\n@vertex\nfn vs_veil(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n var corners = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(0.0, 1.0), vec2f(0.0, 1.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0));\n let k = corners[vi % 6u];\n let vw = t.view.x;\n let vh = t.view.y;\n let lo = clamp(vec2f(t.rect.x, t.rect.y), vec2f(0.0), vec2f(vw, vh));\n let hi = clamp(vec2f(t.rect.x + t.rect.z, t.rect.y + t.shape.y + t.fade.y + t.fade.x + 1.0), vec2f(0.0), vec2f(vw, vh));\n let css = mix(lo, hi, k);\n return vec4f(css.x / vw * 2.0 - 1.0, 1.0 - css.y / vh * 2.0, 0.0, 1.0);\n}\n\n@fragment\nfn fs_veil(@builtin(position) pos: vec4f) -> @location(0) vec4f {\n return tray_veil(u, t, pos.xy, noise_tex, noise_samp, hash_tex);\n}\n";
|
|
14
|
+
readonly "tray/tray.wgsl": "// THE PEGBOARD TRAY (design-017 §5–§6; design-018 §2–§4) — the drawer, and the research board in it (research/sdf-pegboard, shader.js)\n// lying on the mat, shaded in CLOSED FORM for a head-on view: no march, no history, no jitter — every frame is final, so a scrolling\n// drawer is clean on every frame. Its edge is an arris in the face's material; its holes show the desk as drawn under it, black at the\n// board's shadow; under its top edge a clear HEADER (design-018 R4) where nothing hangs and no hole opens, then what hangs on it fades in\n// through the face's portal feather (every kind's `portal_cover`) and here, the accessories and the holes. A PURE module: the uniform blocks arrive as parameters — `u` the mat's struct carrying the DESK's light (the colour law\n// is mat.wgsl's own), `t` the tray's (src/tray/pass.ts) — and so does the blue noise.\n//\n// Board units are PITCHES (the research's 1). A board point is x from the drawer's left edge and Y down the board, Y split into a\n// whole row R (i32: the scroll's whole rows carried on the CPU plus the rows on screen) and fy ∈ [0,1): nothing large ever enters\n// an f32, so the pattern is exact at any scroll (src/tray/lattice.ts is this arithmetic on the CPU, rounding half up as here).\n\n// ---- hashing: the research's pcg, on integer lattice points only (wrapping u32)\nfn peg_pcg(v: u32) -> u32 {\n let s = v * 747796405u + 2891336453u;\n let w = ((s >> ((s >> 28u) + 4u)) ^ s) * 277803737u;\n return (w >> 22u) ^ w;\n}\n\n// Four independent uniforms for a lattice cell (the research's hash2i4).\nfn peg_hash4(x: u32, y: u32, seed: u32) -> vec4f {\n let h0 = peg_pcg(x ^ peg_pcg(y + seed));\n let h1 = peg_pcg(h0);\n let h2 = peg_pcg(h1);\n let h3 = peg_pcg(h2);\n return vec4f(f32(h0), f32(h1), f32(h2), f32(h3)) * 2.3283064365386963e-10;\n}\n\n// ---- the board point\nstruct PegPoint { x: f32, R: i32, fy: f32 }\n\n// The same point moved by `d` pitches — the move's rows re-carried into R, so R stays exact.\nfn peg_move(p: PegPoint, d: vec2f) -> PegPoint {\n let y = p.fy + d.y;\n let fl = floor(y);\n return PegPoint(p.x + d.x, p.R + i32(fl), y - fl);\n}\n\n// ---- value noise on the board (the research's noise2, its quintic fade): the value and its gradient in lattice units. The corners'\n// hashes come PRE-GATHERED (src/tray/pass.ts `hashTexels`: texel (i, j) holds the research's pcg hash of lattice points (i, j),\n// (i+1, j), (i, j+1), (i+1, j+1), 8 bits each, tiled every 256 cells) — one load where the pcg took eight rounds. Each band reads\n// it at its own offset (its seed); the lattice point is an integer, so the noise stays exact at any row.\nfn peg_vnoise(ht: texture_2d<f32>, x: u32, y: u32, f: vec2f, seed: u32) -> vec3f {\n let u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);\n let du = 30.0 * f * f * (f * (f - 2.0) + 1.0);\n let k = textureLoad(ht, vec2u((x + seed) & 255u, (y + ((seed * 2654435761u) >> 24u)) & 255u), 0);\n let a = k.r;\n let b = k.g;\n let c = k.b;\n let d = k.a;\n let v = mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n return vec3f(v, du.x * mix(b - a, d - c, u.y), du.y * mix(c - a, d - b, u.x));\n}\n\n// One band at (fx, fy) cells per pitch (integers): its lattice row is R·fy plus the cells within the row, in wrapping u32 — seamless\n// and exact at any R. The value, and its gradient in pitches.\nfn peg_band(ht: texture_2d<f32>, p: PegPoint, fx: u32, fy: u32, seed: u32) -> vec3f {\n let X = p.x * f32(fx);\n let ix = floor(X);\n let Y = p.fy * f32(fy);\n let iy = floor(Y);\n let n = peg_vnoise(ht, u32(i32(ix)), u32(p.R) * fy + u32(i32(iy)), vec2f(X - ix, Y - iy), seed);\n return vec3f(n.x, n.y * f32(fx), n.z * f32(fy));\n}\n\n// ⌊a / b⌋ for b > 0 (WGSL's `/` truncates toward zero).\nfn peg_floor_div(a: i32, b: i32) -> i32 {\n let q = a / b;\n return select(q, q - 1, (a % b != 0) && (a < 0));\n}\n\n// Value noise on a TURNED lattice, exact at any row: lattice coordinates M·(x, Y)·num/den, M = [[a, −b], [b, a]] an INTEGER rotation\n// and scale (cells turned atan(b/a), num·√(a² + b²)/den of them to a pitch), so no octave's cells line up with the board or with\n// another octave's — value noise's square cells stop reading as squares. The carried row splits R = den·Q + r: Q's share is an\n// exact integer (−b·num·Q, a·num·Q); only x, r and fy, all small, are ever a float. One pre-gathered load.\nfn peg_rot(ht: texture_2d<f32>, p: PegPoint, a: i32, b: i32, num: i32, den: i32, seed: u32) -> f32 {\n let q = peg_floor_div(p.R, den);\n let r = f32(p.R - q * den) + p.fy;\n let uv = vec2f(f32(a) * p.x - f32(b) * r, f32(b) * p.x + f32(a) * r) * (f32(num) / f32(den));\n let cell = floor(uv);\n let base = vec2i(i32(cell.x) - b * num * q, i32(cell.y) + a * num * q);\n return peg_vnoise(ht, u32(base.x), u32(base.y), uv - cell, seed).x;\n}\n\n// The research's \"fine detail fading with pixel footprint\", per band: F cycles per pitch are kept while a cycle spans ≥ 4 device\n// px and gone at 2, so a scrolling board never shimmers (a fleck by its thin side, not its cell). The footprint is the frame's, so\n// every band's share is a UNIFORM (pass.ts `keep` — `1 − smoothstep(¼, ½, fp·F)`): the face's grain (≈ 19) · 85 · 190, then\n// 210 · 42 · 105, the punched fibre's 9 · 18 · 36.\n\n// Sparse fibre flecks (the research's `flecks`): one randomly turned ellipse per jittered cell at F per pitch, signed — dark\n// negative, pale positive. A stroke's thin side is sub-pixel long before its cell is (≈ 0.3 of it): its edge is widened by the pixel's\n// footprint in the ellipse's own measure and its peak lowered to keep its ink — analytic AA, the stroke a faint line, never a dash\n// (the research averaged 1024 jittered samples to the same end). `fp` = pitches per device px.\nfn peg_flecks(p: PegPoint, F: u32, density: f32, seed: u32, fp: f32) -> f32 {\n let X = p.x * f32(F);\n let ix = floor(X);\n let Y = p.fy * f32(F);\n let iy = floor(Y);\n let cx = u32(i32(ix));\n let cy = u32(p.R) * F + u32(i32(iy));\n let q = vec2f(X - ix, Y - iy);\n var acc = 0.0;\n for (var j = -1; j <= 1; j++) {\n for (var i = -1; i <= 1; i++) {\n let r = peg_hash4(cx + u32(i), cy + u32(j), seed);\n if (r.w > density) { continue; }\n let d = q - (vec2f(f32(i), f32(j)) + r.xy);\n let ang = r.z * 3.14159265;\n let ca = cos(ang);\n let sa = sin(ang);\n let dl = vec2f(d.x * ca + d.y * sa, -d.x * sa + d.y * ca);\n let wid = 0.10 + 0.10 * r.y;\n let e = length(vec2f(dl.x / (0.35 + 0.65 * r.x), dl.y / wid));\n let aa = fp * f32(F) / wid;\n acc += (1.0 - smoothstep(0.6 - aa, 1.0 + aa, e)) / (1.0 + 2.0 * aa) * select(-1.0, 0.8, r.z > 0.62);\n }\n }\n return clamp(acc, -1.0, 1.0);\n}\n\n// ---- the lattice (the research's cellCoord · sdStadiumV)\nstruct PegHole { q: vec2f, d: f32, g: vec2f }\n\n// The vertical stadium at an offset from its centre: the distance (negative inside) and its gradient, pointing out of the hole.\nfn peg_stadium(t: TrayUniforms, q: vec2f) -> vec3f {\n let e = vec2f(q.x, q.y - clamp(q.y, -t.hole.y, t.hole.y));\n let len = max(length(e), 1.0e-6);\n return vec3f(len - t.hole.x, e / len);\n}\n\n// The hole of the nearest row (a hole's half-height, 0.33, is under half a pitch): rows at Y = r + ¾, odd rows shifted ½ — the\n// stagger's parity from the exact row index. A hole whose centre lies within the solid border of a side is never punched.\nfn peg_hole(t: TrayUniforms, p: PegPoint) -> PegHole {\n let dr = floor(p.fy - t.depth.w + 0.5);\n let row = p.R + i32(dr);\n let shift = t.depth.z + select(0.0, 0.5, (row & 1) != 0);\n let hx = p.x - shift;\n let col = floor(hx + 0.5);\n var h: PegHole;\n h.q = vec2f(hx - col, p.fy - dr - t.depth.w);\n let s = peg_stadium(t, h.q);\n h.d = s.x;\n h.g = s.yz;\n let cx = col + shift;\n if ((cx < t.hole.w) || (cx > t.rect.z / t.view.w - t.hole.w)) { h.d = 1.0e3; }\n return h;\n}\n\n// ---- the materials (the research's boardMat · wallMat), linear albedo\n// The tempered face: the TONE drift — two smooth octaves on lattices turned against the board and each other (cells 2.24 and 0.89\n// of a pitch), the research's big and mid fbm as a UI pitch shows them — and the fine fibre GRAIN on a third (cells 0.05 of a pitch,\n// ≈ 19 a pitch: what survives of the research's fine grain where its footprint lets it), then at a reading pitch the research's fine\n// height grain and its bump, the fibre flecks — every band faded by its own footprint. Out: the grain as one factor on the face's\n// linear albedo (`g`: the albedo is face·(1 + g)), the height's gradient (the bump), and whether the face is FLAT (every bump faded).\nstruct PegFace { g: f32, grad: vec2f, flat: bool }\nfn peg_face(ht: texture_2d<f32>, t: TrayUniforms, p: PegPoint, fp: f32) -> PegFace {\n let tone = 0.62 * (peg_rot(ht, p, 2, 1, 1, 5, 0x3a1u) - 0.5) + 0.38 * (peg_rot(ht, p, 1, -2, 1, 2, 0x5c7u) - 0.5);\n var grain = 0.0;\n let kg = t.keepFace.x;\n if (kg > 0.0) { grain = kg * (peg_rot(ht, p, 3, 1, 6, 1, 0x2b1u) - 0.5); }\n // the fine height (the research's faceHeight: 0.55 · 85, 0.30 · 190×70, 0.15 · 60×210) — at a UI pitch its footprint fades it\n var h = 0.0;\n var grad = vec2f(0.0);\n let k85 = t.keepFace.z;\n let k190 = t.keepFace.w;\n let k210 = t.keepFine.x;\n if (k85 > 0.0) { let n = peg_band(ht, p, 85u, 85u, 0x1f3u); h += 0.55 * k85 * (n.x - 0.5); grad += 0.55 * k85 * n.yz; }\n if (k190 > 0.0) { let n = peg_band(ht, p, 190u, 70u, 0x4a7u); h += 0.30 * k190 * (n.x - 0.5); grad += 0.30 * k190 * n.yz; }\n if (k210 > 0.0) { let n = peg_band(ht, p, 60u, 210u, 0x7c9u); h += 0.15 * k210 * (n.x - 0.5); grad += 0.15 * k210 * n.yz; }\n let k42 = t.keepFine.y;\n let k105 = t.keepFine.z;\n var fl = 0.0;\n if (k42 > 0.0) { fl += 0.30 * k42 * peg_flecks(p, 42u, 0.40, 0xa13u, fp); }\n if (k105 > 0.0) { fl += 0.18 * k105 * peg_flecks(p, 105u, 0.35, 0xc35u, fp); }\n var f: PegFace;\n f.g = (1.0 + 0.12 * tone + 0.08 * grain + 0.40 * h) * (1.0 + fl) - 1.0;\n f.grad = grad;\n f.flat = (k85 <= 0.0) && (k190 <= 0.0) && (k210 <= 0.0);\n return f;\n}\n\n// The paler, fuzzier punched fibre (the research's edge): its clumps at 9 · 18 · 36 per pitch, faded by the footprint.\nfn peg_edge(ht: texture_2d<f32>, t: TrayUniforms, p: PegPoint, fp: f32) -> vec3f {\n var clump = 0.0;\n let k9 = t.keepEdge.x;\n let k18 = t.keepEdge.y;\n let k36 = t.keepEdge.z;\n if (k9 > 0.0) { clump += 0.5 * k9 * (peg_band(ht, p, 9u, 9u, 0x3e5u).x - 0.5); }\n if (k18 > 0.0) { clump += 0.25 * k18 * (peg_band(ht, p, 18u, 18u, 0x5a9u).x - 0.5); }\n if (k36 > 0.0) { clump += 0.125 * k36 * (peg_band(ht, p, 36u, 36u, 0x7bdu).x - 0.5); }\n return t.edge.xyz * (1.0 + 0.30 * clump / 0.875);\n}\n\n// ---- the light: the research's HOME lamp (D-K3.4), in the desk's colour law and room\n// The desk's colour law on the tray (the paper's `paper_colour`, chain 0): by day the mat's grade on the sRGB albedo — lit (`gobo`\n// 1) the identity, a configured byte is the drawn byte; in shadow the room's ½ and its touch of saturation — by night the Moon and\n// the eye (mat.wgsl `night_mat`) on the linear albedo; between, the cross-fade.\nfn tray_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32) -> vec3f {\n let day = shade_mat(u, albedo, gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = night_mat(u, srgb_to_linear(albedo), gobo, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// A surface under the lamp, its linear albedo and normal → the colour on screen. The relief is the paper's — `(N·L/L_z)^(1/2.2)`, the\n// flat face exactly 1 — and only brightens; a normal turned from the lamp takes the room's grade instead (`gobo` = the Lambert term),\n// so a face turned away reads as a shadow does, never black. `vis`: the lamp seen at all. The glint is the photo's.\nfn tray_lit(u: MatUniforms, t: TrayUniforms, alb: vec3f, n: vec3f, vis: f32, noise: f32) -> vec3f {\n let L = t.lamp.xyz;\n let lam = clamp(dot(n, L) / max(L.z, 1.0e-3), 0.0, 1.6);\n let srgb = clamp(night_encode(alb) * pow(max(lam, 1.0), 1.0 / 2.2), vec3f(0.0), vec3f(1.0));\n var c = tray_colour(u, srgb, min(lam, 1.0) * vis, noise);\n let H = normalize(L + vec3f(0.0, 0.0, 1.0));\n let spec = pow(saturate(dot(n, H)), 48.0) * 0.08 * vis;\n let glint = mix(vec3f(1.0, 0.985, 0.95), vec3f(0.78, 0.86, 1.0) * 0.5, saturate(u.night.x));\n return c + glint * spec;\n}\n\n// ---- the board: the face and the rim fillet, and the desk through a hole\n// The front surface at a board point: the face, or — within k of a hole — the fillet. On smax(slab, −hole, k) = 0 seen head-on the\n// surface's blend weight is h = √(d/k) for d ∈ [0,k], and its normal normalize(((h − 1)·∇d, h)) sweeps from the hole's wall to the\n// face: the rim's lit edge and its shadowed one, in the paler punched fibre where the normal is steep (the research's faceness).\nfn peg_surface(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: PegPoint, h: PegHole, fp: f32, noise: f32) -> vec3f {\n let k = t.hole.z;\n let face = peg_face(ht, t, p, fp);\n // the PLAIN FACE — clear of every fillet, its bump faded: N·L is L_z exactly, the relief 1, the glint nothing; the grain's small\n // linear factor rides the configured byte to first order ((1 + g)^(1/2.4) ≈ 1 + g/2.4 — the byte itself where the grain is 0)\n if ((h.d >= k) && face.flat) {\n return tray_colour(u, clamp(t.faceSrgb.xyz * (1.0 + face.g / 2.4), vec3f(0.0), vec3f(1.0)), 1.0, noise);\n }\n var n = normalize(vec3f(-face.grad * 0.003, 1.0)); // the research's bump: 0.0030 per unit of the height's slope\n if (h.d < k) {\n let s = sqrt(max(h.d, 0.0) / k);\n n = normalize(vec3f((s - 1.0) * h.g, s));\n }\n let faceness = smoothstep(0.35, 0.75, n.z);\n var alb = t.face.xyz * (1.0 + face.g);\n if (faceness < 1.0) { alb = mix(peg_edge(ht, t, p, fp) * 0.86, alb, faceness); } // the punched rim, burnished at the front\n return tray_lit(u, t, alb, n, 1.0, noise);\n}\n\n// Through a hole: THE DESK AS DRAWN (design-018 §3). A hole is cut through the board, so it shows what lies under the drawer this\n// frame — the mat's green, its grid lines, the leaf shadows, anything laid there — in the board's shadow: premultiplied BLACK over the\n// frame, at the alpha that leaves `f` of the light. THE TWO PLANES (design-017 §6.4) give the lamp at the mat: the ray from the mat\n// toward the lamp crosses the board's back face at A and its front face at B; the mat is lit iff both lie in the SAME hole — B's, with\n// A tested against it (a convex prism holds the segment iff it holds both ends) — the penumbra from the SDF distance, widened by the\n// lamp's size over each plane's height. The room's light reaches the mat through the hole, cut by the cavity. f = a·cav + (1 − a)·vis,\n// `a` the room's share (the mat's own shade-in-shadow: shade_mat at gobo 0) — ENCODED as the mat's own chain encodes its shade (its\n// grade lives in pow-2.2 space), so the densest hole is the mat's shade under a leaf — over the desk dimmed as the rest of it is.\nfn peg_through(u: MatUniforms, t: TrayUniforms, p: PegPoint, h: PegHole, fp: f32) -> f32 {\n let L = t.lamp.xyz;\n let lz = max(L.z, 1.0e-3);\n let off = L.xy / lz;\n let thick = t.depth.x;\n let gap = t.depth.y;\n let hb = peg_hole(t, peg_move(p, off * (gap + thick)));\n let da = peg_stadium(t, hb.q - off * thick).x;\n // the penumbra: the lamp's angular radius over the ray's length to each plane's edge (height / L_z), and a pixel\n let wa = t.lamp.w * gap / lz + fp;\n let wb = t.lamp.w * (gap + thick) / lz + fp;\n let vis = smoothstep(-wb, wb, -hb.d) * smoothstep(-wa, wa, -da);\n let cav = mix(t.cavity.x, t.cavity.y, smoothstep(0.0, t.cavity.z, -h.d));\n let a = 1.0 - u.gobo.z * (1.0 - u.gobo.w);\n let f = a * cav + (1.0 - a) * vis;\n return 1.0 - (1.0 - t.dim) * pow(f, 1.0 / 2.2);\n}\n\n// ---- the drawer\nfn tray_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let k = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * k - 1.453152027) * k) + 1.421413741) * k - 0.284496736) * k + 0.254829592) * k * exp(-a * a);\n return s * y;\n}\n\n// Gaussian-blurred coverage of an outline at distance d (the shadow, straight out of the field — the paper's).\nfn tray_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * tray_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n\n// The drawer's outline (CSS px, negative inside): a box whose top corners round and whose bottom lies past the view — what is drawn\n// and what casts the shadows (the finger notch retired — design-018 §2).\nfn tray_outline(t: TrayUniforms, p: vec2f) -> f32 {\n let r = t.shape.x;\n let ext = vec2f(0.5 * t.rect.z, 0.5 * t.rect.w + r);\n return sdf_round_box(p - vec2f(t.rect.x + ext.x, t.rect.y + ext.y), ext, r);\n}\n\n// The board point under a pixel (CSS px) — the rows on screen added to the carried rows in i32.\nfn tray_point(t: TrayUniforms, p: vec2f) -> PegPoint {\n let ly = (p.y - t.rect.y) / t.view.w + t.frac;\n let fl = floor(ly);\n return PegPoint((p.x - t.rect.x) / t.view.w, t.rowBase + i32(fl), ly - fl);\n}\n\n// The board's EDGE (design-018 §2): an ARRIS — a quarter-round `A` px wide where the face meets the outline, in the FACE's own\n// material (its tone at the board point under it, so the grain runs on without a seam; not the paler punched fibre). Its normal turns\n// from the eye (on the face's side) to the outline's outward direction (at the outline): it catches the lamp where the outline faces\n// it — the top and the left — and falls to the room's shade on the far side. `o`: the outline's distance there (−A … 0).\nfn tray_arris(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, o: f32, noise: f32) -> vec3f {\n let e = 0.5;\n let gx = tray_outline(t, p + vec2f(e, 0.0)) - tray_outline(t, p - vec2f(e, 0.0));\n let gy = tray_outline(t, p + vec2f(0.0, e)) - tray_outline(t, p - vec2f(0.0, e));\n let g = vec2f(gx, gy) / max(length(vec2f(gx, gy)), 1.0e-6);\n let s = clamp(1.0 + o / t.shape.y, 0.0, 1.0);\n let n = vec3f(s * g, sqrt(max(1.0 - s * s, 0.0)));\n let face = peg_face(ht, t, tray_point(t, p), t.fp);\n return tray_lit(u, t, t.face.xyz * (1.0 + face.g), n, 1.0, noise);\n}\n\n// The board to its edge, PREMULTIPLIED: the point under the carry, its hole — the front surface over it by its analytic coverage, and\n// where the surface is not, the desk seen through (black at the hole's alpha). `holes` scales every hole's OPENING: 1 as punched (the\n// board under the specimens), 0 CLOSED — the plain face, its fillets' relief flattened into it (the VEIL's board, `tray_veil`).\nfn tray_board(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, noise: f32, holes: f32) -> vec4f {\n let pt = tray_point(t, p);\n let h = peg_hole(t, pt);\n let cg = clamp(0.5 + h.d / t.fp, 0.0, 1.0); // the surface's analytic coverage as punched\n let open = (1.0 - cg) * holes; // what of the pixel the hole opens\n var col = vec4f(0.0);\n if (holes < 1.0) {\n // closing: the plain face where the hole no longer opens, and the fillet (within `hole.z` of it) turning to the face\n var plain = h;\n plain.d = 1.0e3;\n let face = peg_surface(ht, u, t, pt, plain, t.fp, noise);\n var s = face;\n if (cg > 0.0 && h.d < t.hole.z) { s = mix(face, peg_surface(ht, u, t, pt, h, t.fp, noise), holes); }\n col = vec4f(s, 1.0) * cg + vec4f(face, 1.0) * ((1.0 - cg) * (1.0 - holes));\n } else if (cg > 0.0) {\n col = vec4f(peg_surface(ht, u, t, pt, h, t.fp, noise), 1.0) * cg;\n }\n if (open > 0.0) { col += vec4f(0.0, 0.0, 0.0, peg_through(u, t, pt, h, t.fp) * open); }\n return col;\n}\n\n// A pixel INSIDE the drawer's outline (`o` its distance there, `px` a device px in CSS px), premultiplied: the edge within `A` of the\n// outline, the board inside it with its holes at `holes` — blended across the pixel where they meet — and the edge's INNER SHADOW over\n// the board (D-R1.2): the window's lip on the lamp's side, black at up to `shape.z`, fading over `shape.w` px inside the edge, where the\n// outline faces the lamp (the push of the drawer's lamp shadow points away from it).\nfn tray_inside(ht: texture_2d<f32>, u: MatUniforms, t: TrayUniforms, p: vec2f, px: f32, o: f32, noise: f32, holes: f32) -> vec4f {\n let k = clamp(0.5 + (o + t.shape.y) / px, 0.0, 1.0);\n var col = vec4f(0.0);\n if (k > 0.0) { col = vec4f(tray_arris(ht, u, t, p, o, noise), 1.0); }\n if (k < 1.0) {\n var b = tray_board(ht, u, t, p, noise, holes);\n if (t.shape.z > 0.0 && -o - t.shape.y < t.shape.w) {\n let e = 0.5;\n let g = normalize(vec2f(tray_outline(t, p + vec2f(e, 0.0)) - tray_outline(t, p - vec2f(e, 0.0)), tray_outline(t, p + vec2f(0.0, e)) - tray_outline(t, p - vec2f(0.0, e))) + vec2f(1.0e-6, 0.0));\n let facing = max(dot(g, -normalize(t.shadow.zw)), 0.0);\n let sh = t.shape.z * facing * (1.0 - smoothstep(0.0, t.shape.w, -o - t.shape.y));\n b = vec4f(b.rgb * (1.0 - sh), 1.0 - (1.0 - b.a) * (1.0 - sh));\n }\n col = mix(b, col, k);\n }\n return col;\n}\n\n// One pixel of the tray UNDER its specimens (`frag` in device px), the view whole: the dim over the desk, the drawer's shadows on it,\n// the board to the outline and its edge, every hole as punched — premultiplied, each pixel drawn once. The board scrolls under its edge\n// (the drawer is a window onto it). The specimens hang over the board inside the edge and under the header (their face clip,\n// tray/specimens.ts `faceClip`); the VEIL (`tray_veil`) is laid over them all at the top.\nfn tray_drawer(u: MatUniforms, t: TrayUniforms, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let dpr = t.view.z;\n let p = frag / dpr;\n let px = 1.0 / dpr;\n let dim = vec4f(0.0, 0.0, 0.0, t.dim);\n // beyond the shadows' reach: the dim alone\n let reach = 3.0 * max(t.shadow.x, t.room.x) + length(t.shadow.zw);\n if ((p.x < t.rect.x - reach) || (p.x > t.rect.x + t.rect.z + reach) || (p.y < t.rect.y - reach)) { return dim; }\n // the blue noise is the NIGHT's (the rods' snow, mat.wgsl `night_mat`): by day the board takes none — its own grain never bands,\n // and a scrolled board is then the same pixels, moved\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n // DEEP INSIDE — clear of the rounded corners, the edge, its inner shadow and two px more: no edge, no shadow, no outline to evaluate\n // (premultiplied — a hole's alpha is its shadow over the desk, no longer 1)\n let m = t.shape.y + t.shape.w + 2.0;\n if ((p.x > t.rect.x + m) && (p.x < t.rect.x + t.rect.z - m) && (p.y > t.rect.y + t.shape.x + m)) {\n return tray_board(ht, u, t, p, bn.y, 1.0);\n }\n let o = tray_outline(t, p);\n let cover = clamp(0.5 - o / px, 0.0, 1.0);\n // the shadows on the desk — the room's round the outline, the lamp's pushed along its ground direction, one over the other — where\n // the drawer does not cover them\n var under = dim;\n if (cover < 1.0) {\n let room = t.room.y * tray_blur(o, t.room.x);\n let lamp = t.shadow.y * tray_blur(tray_outline(t, p - t.shadow.zw), t.shadow.x);\n under = vec4f(0.0, 0.0, 0.0, 1.0 - (1.0 - room) * (1.0 - lamp) * (1.0 - t.dim));\n }\n if (cover <= 0.0) { return under; }\n return tray_inside(ht, u, t, p, px, o, bn.y, 1.0) * cover + under * (1.0 - cover);\n}\n\n// THE VEIL (design-018 rev 5 — James, 2026-09-28: \"at the edge fading, somehow the holes are displayed on top of objects\"): the PLAIN\n// board — its face with every hole closed, its edge, the edge's inner shadow: `tray_drawer`'s own pixel, less its holes — laid OVER\n// everything the drawer holds (the accessories, the specimens, their tags), whole in the HEADER (`fade.y` under the edge's inside) and\n// fading out over the ramp below it (`fade.x`): `1 − smoothstep(foot, foot + fade.x, y)`. What hangs there and the holes behind it fade\n// INTO THE BOARD together — a specimen never turns see-through, so no hole shows through it (the specimens are opaque to the face\n// clip's top; a hard clip at the header's foot lies under the veil's whole). Only pixels wholly inside the outline: its antialiased rim\n// is `tray_drawer`'s, and the veil there would lay the edge twice.\nfn tray_veil(u: MatUniforms, t: TrayUniforms, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let p = frag / t.view.z;\n let px = 1.0 / t.view.z;\n let foot = t.rect.y + t.shape.y + t.fade.y;\n let a = 1.0 - smoothstep(foot, foot + t.fade.x, p.y);\n if (a <= 0.0) { return vec4f(0.0); }\n let o = tray_outline(t, p);\n if (o > -0.5 * px) { return vec4f(0.0); }\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n return tray_inside(ht, u, t, p, px, o, bn.y, 0.0) * a;\n}\n\n// ---- the accessories (K5a): what the specimens hang on, plugged into real holes — SKÅDIS's hook, shelf, clip and rail seen head-on, in\n// their powder coat under the lamp, each casting its shadow on the board. One instance per specimen (tray-pass.wgsl), its quad the\n// accessory and its shadow's reach; CSS px, the pitch `P` the unit of every length (kernel/tray.ts TRAY_ACCESSORY's numbers).\nfn acc_peg(a: TrayAccessory, i: u32) -> vec2f {\n if (i == 0u) { return a.pegs0.xy; }\n if (i == 1u) { return a.pegs0.zw; }\n if (i == 2u) { return a.pegs1.xy; }\n return a.pegs1.zw;\n}\n\n// The accessory's silhouette: its signed distance at `p` (negative inside) — every peg's PLUG in its hole, and the accessory's body.\nfn acc_sdf(a: TrayAccessory, p: vec2f, P: f32) -> f32 {\n let kind = u32(a.kind.x + 0.5);\n let n = min(u32(a.kind.y + 0.5), 4u);\n let r = a.rect;\n var d = 1.0e4;\n var lo = 1.0e9;\n var hi = -1.0e9;\n for (var i = 0u; i < n; i++) {\n let g = acc_peg(a, i);\n lo = min(lo, g.x);\n hi = max(hi, g.x);\n d = min(d, sdf_round_box(p - g, vec2f(0.1, 0.28) * P, 0.09 * P)); // the plug, in its hole\n if (kind == 0u) { d = min(d, sdf_segment(p, g, g + vec2f(0.0, 0.58 * P)) - 0.07 * P); } // a hook: down to its tip\n if (kind == 1u) { d = min(d, sdf_segment(p, g, vec2f(g.x, r.w + 0.1 * P)) - 0.06 * P); } // a shelf's bracket, up to the plank\n if (kind == 2u) { // a clip: its spring from the hole down over the specimen's top edge\n let top = g.y - 0.22 * P;\n let foot = max(g.y, r.y) + 0.5 * P;\n d = min(d, sdf_round_box(p - vec2f(g.x, 0.5 * (top + foot)), vec2f(0.24 * P, 0.5 * (foot - top)), 0.08 * P));\n }\n }\n if (kind == 1u) { // the shelf's plank, under the specimen and past its sides\n let hw = 0.5 * (r.z - r.x) + 0.3 * P;\n d = min(d, sdf_round_box(p - vec2f(0.5 * (r.x + r.z), r.w + 0.12 * P), vec2f(hw, 0.12 * P), 0.05 * P));\n }\n if (kind == 3u && n > 0u) { // the rail: a bar through its holes, past the outer two\n let y = acc_peg(a, 0u).y;\n d = min(d, sdf_round_box(p - vec2f(0.5 * (lo + hi), y), vec2f(0.5 * (hi - lo) + 0.4 * P, 0.11 * P), 0.11 * P));\n }\n return d;\n}\n\n// One pixel of an accessory's quad: its shadow on the board (the silhouette seen from the lamp, pushed off by how far it stands off the\n// board, softened by the lamp's size over that), then the accessory over it — its rounded relief from the silhouette's gradient, lit as\n// the board's edge is; both kept inside that edge. Premultiplied.\nfn tray_accessory(u: MatUniforms, t: TrayUniforms, a: TrayAccessory, frag: vec2f, noise_tex: texture_2d<f32>, noise_samp: sampler, ht: texture_2d<f32>) -> vec4f {\n let p = frag / t.view.z;\n let px = 1.0 / t.view.z;\n let P = t.view.w;\n // inside the board's edge — whole: in the header and its ramp the VEIL lays the plain board over it, as over the specimens (design-018\n // rev 5, `tray_veil`)\n let inside = clamp(0.5 - (tray_outline(t, p) + t.shape.y) / px, 0.0, 1.0);\n if (inside <= 0.0) { return vec4f(0.0); }\n let L = t.lamp.xyz;\n let lz = max(L.z, 1.0e-3);\n let off = L.xy / lz * a.kind.z;\n let sig = t.lamp.w * a.kind.z / lz + px;\n let ds = acc_sdf(a, p + off, P);\n let shadow = t.accessory.w * (1.0 - smoothstep(-sig, sig, ds));\n let d = acc_sdf(a, p, P);\n let c = clamp(0.5 - d / px, 0.0, 1.0);\n if ((c <= 0.0) && (shadow <= 1.0e-4)) { return vec4f(0.0); }\n var col = vec3f(0.0);\n if (c > 0.0) {\n let e = 0.5;\n let gx = acc_sdf(a, p + vec2f(e, 0.0), P) - acc_sdf(a, p - vec2f(e, 0.0), P);\n let gy = acc_sdf(a, p + vec2f(0.0, e), P) - acc_sdf(a, p - vec2f(0.0, e), P);\n let g = vec2f(gx, gy) / max(length(vec2f(gx, gy)), 1.0e-6);\n let s = clamp(-d / (0.07 * P), 0.0, 1.0);\n let n = normalize(vec3f(g * (1.0 - s) * 1.2, max(s, 0.2)));\n var bn = vec3f(0.5);\n if (u.night.x > 0.0) { bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb; }\n col = tray_lit(u, t, t.accessory.xyz, n, 1.0, bn.y);\n }\n let body = c * inside;\n let under = shadow * inside;\n return vec4f(col * body, body + under * (1.0 - body));\n}\n";
|
|
15
|
+
};
|
|
16
|
+
export type WgslFile = keyof typeof WGSL;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface UploadTally {
|
|
2
|
+
/** `writeBuffer` + `writeTexture` + `copyExternalImageToTexture` calls. */
|
|
3
|
+
readonly writes: number;
|
|
4
|
+
/** The bytes those calls carried (a texture write counts what was handed to it; an external copy its texels). */
|
|
5
|
+
readonly bytes: number;
|
|
6
|
+
}
|
|
7
|
+
export interface SubmitInstrument {
|
|
8
|
+
/** Every `queue.submit` since install or the last `reset`, whoever made it. */
|
|
9
|
+
total(): number;
|
|
10
|
+
/** Command buffers passed across all those submits. */
|
|
11
|
+
buffers(): number;
|
|
12
|
+
/** Submits in the trailing `windowMs`, from the stamp ring. */
|
|
13
|
+
inWindow(windowMs: number): number;
|
|
14
|
+
/** Timestamps of the retained submits (newest last), for tail inspection. */
|
|
15
|
+
stamps(): readonly number[];
|
|
16
|
+
/** Every upload since install or the last `reset` — `writeBuffer`, `writeTexture`, `copyExternalImageToTexture` — and the bytes they carried. */
|
|
17
|
+
uploads(): UploadTally;
|
|
18
|
+
/** The uploads by the written resource's label prefix (the text before its `/`; `?` for an unlabelled one). A fresh copy. */
|
|
19
|
+
uploadsByLabel(): Readonly<Record<string, UploadTally>>;
|
|
20
|
+
/** The `copyExternalImageToTexture` uploads alone (a picture decoded onto the GPU, a calendar tile printed) — also inside `uploads`. */
|
|
21
|
+
externals(): UploadTally;
|
|
22
|
+
reset(): void;
|
|
23
|
+
/** Put back what the queue had before the install — its own `writeBuffer`/`writeTexture`/`copyExternalImageToTexture`, and its `submit` once no other listener taps it. Idempotent. */
|
|
24
|
+
detach(): void;
|
|
25
|
+
}
|
|
26
|
+
/** One listener on a queue's submits: `before` sees the list as handed in (materialised once), `after` runs once the native submit returned. */
|
|
27
|
+
export interface SubmitHook {
|
|
28
|
+
before?(list: readonly GPUCommandBuffer[]): void;
|
|
29
|
+
after?(list: readonly GPUCommandBuffer[]): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* THE SUBMIT TAP (K2): ONE wrapper on `queue.submit`, shared by every instrument that listens to submits — installed with the first
|
|
33
|
+
* hook, removed with the last (the queue's own `submit` back). Returns the hook's removal; idempotent.
|
|
34
|
+
*/
|
|
35
|
+
export declare function tapSubmits(queue: GPUQueue, hook: SubmitHook): () => void;
|
|
36
|
+
export declare function instrumentSubmits(device: GPUDevice): SubmitInstrument;
|