@vibecook/ice 0.11.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.
Files changed (381) hide show
  1. package/README.md +79 -50
  2. package/dist/chunk-BA7XJC27.js +2429 -0
  3. package/dist/chunk-BA7XJC27.js.map +1 -0
  4. package/dist/chunk-BKV4ZEU3.js +1 -0
  5. package/dist/chunk-BKV4ZEU3.js.map +1 -0
  6. package/dist/{chunk-VGVDJZSP.js → chunk-DYK6KNLG.js} +13206 -7387
  7. package/dist/chunk-DYK6KNLG.js.map +1 -0
  8. package/dist/chunk-ENIFIUEP.js +155 -0
  9. package/dist/chunk-ENIFIUEP.js.map +1 -0
  10. package/dist/chunk-IBWWLA6D.js +634 -0
  11. package/dist/chunk-IBWWLA6D.js.map +1 -0
  12. package/dist/{chunk-OR5FNVUE.js → chunk-JS6PG6YD.js} +398 -167
  13. package/dist/chunk-JS6PG6YD.js.map +1 -0
  14. package/dist/chunk-QXOCF56A.js +148 -0
  15. package/dist/chunk-QXOCF56A.js.map +1 -0
  16. package/dist/chunk-ZPUPBULB.js +7399 -0
  17. package/dist/chunk-ZPUPBULB.js.map +1 -0
  18. package/dist/desk-engine.js +36 -0
  19. package/dist/desk-engine.js.map +1 -0
  20. package/dist/desk-kit.js +244 -0
  21. package/dist/desk-kit.js.map +1 -0
  22. package/dist/desk-objects.js +12728 -0
  23. package/dist/desk-objects.js.map +1 -0
  24. package/dist/desk.js +469 -0
  25. package/dist/desk.js.map +1 -0
  26. package/dist/devtools.js +233 -189
  27. package/dist/devtools.js.map +1 -1
  28. package/dist/dom.js +7 -19
  29. package/dist/index.js +232 -20
  30. package/dist/kernel.js +53 -25
  31. package/dist/react.js +1045 -38
  32. package/dist/react.js.map +1 -1
  33. package/dist/types/packages/core/src/behavior/define-behavior.d.ts +11 -0
  34. package/dist/types/packages/core/src/behavior/runtime.d.ts +4 -0
  35. package/dist/types/packages/core/src/canvas/define-canvas-type.d.ts +86 -0
  36. package/dist/types/packages/core/src/canvas/define-container.d.ts +17 -0
  37. package/dist/types/packages/core/src/canvas/diagnostics.d.ts +27 -0
  38. package/dist/types/packages/core/src/canvas/engine-catalog.d.ts +70 -0
  39. package/dist/types/packages/core/src/canvas/extensions.d.ts +125 -0
  40. package/dist/types/packages/core/src/canvas/frame-behavior-host.d.ts +16 -0
  41. package/dist/types/packages/core/src/canvas/frame-preview.d.ts +79 -0
  42. package/dist/types/packages/core/src/canvas/frame-projection.d.ts +38 -0
  43. package/dist/types/packages/core/src/canvas/frame-view.d.ts +36 -0
  44. package/dist/types/packages/core/src/canvas/migrate.d.ts +14 -0
  45. package/dist/types/packages/core/src/canvas/placement.d.ts +30 -0
  46. package/dist/types/packages/core/src/canvas/presentation-transition.d.ts +100 -0
  47. package/dist/types/packages/core/src/canvas/runtime-extension-host.d.ts +16 -0
  48. package/dist/types/packages/core/src/canvas/session.d.ts +47 -0
  49. package/dist/types/packages/core/src/catalog/camera-derived.d.ts +16 -27
  50. package/dist/types/packages/core/src/catalog/desk.d.ts +326 -0
  51. package/dist/types/packages/core/src/catalog/gesture.d.ts +51 -0
  52. package/dist/types/packages/core/src/catalog/graph.d.ts +2 -2
  53. package/dist/types/packages/core/src/catalog/index.d.ts +2 -0
  54. package/dist/types/packages/core/src/catalog/pointer.d.ts +42 -0
  55. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +7 -0
  56. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +28 -2
  57. package/dist/types/packages/core/src/catalog/surface.d.ts +1 -0
  58. package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +18 -2
  59. package/dist/types/packages/core/src/doc/doc-kit.d.ts +21 -2
  60. package/dist/types/packages/core/src/doc/envelope.d.ts +5 -2
  61. package/dist/types/packages/core/src/doc/schema-migrate.d.ts +1 -1
  62. package/dist/types/packages/core/src/doc/version-gate.d.ts +21 -3
  63. package/dist/types/packages/core/src/engine/commit-sink.d.ts +10 -1
  64. package/dist/types/packages/core/src/engine/engine.d.ts +6 -0
  65. package/dist/types/packages/core/src/engine/frame-control.d.ts +74 -0
  66. package/dist/types/packages/core/src/engine/guests.d.ts +4 -1
  67. package/dist/types/packages/core/src/engine/world-doors.d.ts +12 -0
  68. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +146 -13
  69. package/dist/types/packages/core/src/index.d.ts +33 -12
  70. package/dist/types/packages/core/src/input/queue.d.ts +3 -1
  71. package/dist/types/packages/core/src/interaction/install.d.ts +30 -0
  72. package/dist/types/packages/core/src/interaction/wakes.d.ts +24 -0
  73. package/dist/types/packages/core/src/nav/nav-geometry.d.ts +100 -0
  74. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +47 -4
  75. package/dist/types/packages/core/src/ops/cascade.d.ts +2 -1
  76. package/dist/types/packages/core/src/ops/claims.d.ts +6 -0
  77. package/dist/types/packages/core/src/ops/insert.d.ts +4 -0
  78. package/dist/types/packages/core/src/ops/point-pick.d.ts +4 -1
  79. package/dist/types/packages/core/src/ops/sibling-order.d.ts +14 -4
  80. package/dist/types/packages/core/src/ops/tray.d.ts +48 -0
  81. package/dist/types/packages/core/src/presence/presence-kit.d.ts +5 -0
  82. package/dist/types/packages/core/src/schema/meta.d.ts +10 -0
  83. package/dist/types/packages/core/src/schema/prefab.d.ts +8 -0
  84. package/dist/types/packages/core/src/settings/defaults.d.ts +25 -6
  85. package/dist/types/packages/core/src/surface/gpu-device.d.ts +55 -0
  86. package/dist/types/packages/core/src/systems/camera-sim.d.ts +29 -0
  87. package/dist/types/packages/core/src/systems/chrome.d.ts +15 -23
  88. package/dist/types/packages/core/src/systems/drag-mods.d.ts +34 -0
  89. package/dist/types/packages/core/src/systems/held.d.ts +71 -0
  90. package/dist/types/packages/core/src/systems/insert-ghost.d.ts +2 -0
  91. package/dist/types/packages/core/src/systems/l0-input.d.ts +14 -0
  92. package/dist/types/packages/core/src/systems/l1-pick.d.ts +37 -1
  93. package/dist/types/packages/core/src/systems/l2-arbitrate.d.ts +18 -4
  94. package/dist/types/packages/core/src/systems/l3-behave.d.ts +4 -1
  95. package/dist/types/packages/core/src/systems/l3-claim.d.ts +7 -0
  96. package/dist/types/packages/core/src/systems/l3-connect.d.ts +1 -1
  97. package/dist/types/packages/core/src/systems/l3-drop.d.ts +30 -1
  98. package/dist/types/packages/core/src/systems/l3-marquee.d.ts +2 -1
  99. package/dist/types/packages/core/src/systems/l3-ports.d.ts +1 -1
  100. package/dist/types/packages/core/src/systems/l4-cursor.d.ts +5 -0
  101. package/dist/types/packages/core/src/systems/nav-flight.d.ts +85 -3
  102. package/dist/types/packages/core/src/systems/nav-tap.d.ts +44 -0
  103. package/dist/types/packages/core/src/systems/press-wheel.d.ts +11 -0
  104. package/dist/types/packages/core/src/systems/tray.d.ts +108 -0
  105. package/dist/types/packages/core/src/systems/zoom-through.d.ts +30 -0
  106. package/dist/types/packages/core/src/tools/define-tool.d.ts +10 -0
  107. package/dist/types/packages/core/src/widget/define-widget.d.ts +166 -63
  108. package/dist/types/packages/core/src/widget/equip.d.ts +14 -2
  109. package/dist/types/packages/core/src/widget/held-tools.d.ts +83 -0
  110. package/dist/types/packages/core/src/widget/menu-actions.d.ts +44 -0
  111. package/dist/types/packages/core/src/widget/mount-store.d.ts +21 -46
  112. package/dist/types/packages/core/src/widget/spawn.d.ts +1 -1
  113. package/dist/types/packages/desk/src/assets/blue-noise.gen.d.ts +10 -0
  114. package/dist/types/packages/desk/src/card/card.d.ts +103 -0
  115. package/dist/types/packages/desk/src/compose/ambient.d.ts +59 -0
  116. package/dist/types/packages/desk/src/compose/builder.d.ts +285 -0
  117. package/dist/types/packages/desk/src/compose/children.d.ts +3 -0
  118. package/dist/types/packages/desk/src/compose/index.d.ts +14 -0
  119. package/dist/types/packages/desk/src/compose/marks.d.ts +126 -0
  120. package/dist/types/packages/desk/src/compose/pick.d.ts +5 -0
  121. package/dist/types/packages/desk/src/compose/reflector.d.ts +111 -0
  122. package/dist/types/packages/desk/src/compose/tap.d.ts +5 -0
  123. package/dist/types/packages/desk/src/docs.d.ts +23 -0
  124. package/dist/types/packages/desk/src/engine/budget.d.ts +38 -0
  125. package/dist/types/packages/desk/src/engine/device.d.ts +54 -0
  126. package/dist/types/packages/desk/src/engine/index.d.ts +5 -0
  127. package/dist/types/packages/desk/src/engine/pipeline.d.ts +36 -0
  128. package/dist/types/packages/desk/src/engine/rasters.d.ts +66 -0
  129. package/dist/types/packages/desk/src/engine/records.d.ts +56 -0
  130. package/dist/types/packages/desk/src/engine/shader.d.ts +22 -0
  131. package/dist/types/packages/desk/src/engine/struct.d.ts +39 -0
  132. package/dist/types/packages/desk/src/engine/target.d.ts +26 -0
  133. package/dist/types/packages/desk/src/gpu-ablation.d.ts +61 -0
  134. package/dist/types/packages/desk/src/gpu-memory.d.ts +54 -0
  135. package/dist/types/packages/desk/src/gpu-profiler.d.ts +141 -0
  136. package/dist/types/packages/desk/src/gpu-wrap.d.ts +8 -0
  137. package/dist/types/packages/desk/src/ground.d.ts +388 -0
  138. package/dist/types/packages/desk/src/hold/focus.d.ts +53 -0
  139. package/dist/types/packages/desk/src/hold/pose.d.ts +132 -0
  140. package/dist/types/packages/desk/src/hold/shaders.d.ts +9 -0
  141. package/dist/types/packages/desk/src/host/editor.d.ts +18 -0
  142. package/dist/types/packages/desk/src/host/index.d.ts +9 -0
  143. package/dist/types/packages/desk/src/host/ink.d.ts +18 -0
  144. package/dist/types/packages/desk/src/host/layer.d.ts +430 -0
  145. package/dist/types/packages/desk/src/host/picture.d.ts +2 -0
  146. package/dist/types/packages/desk/src/host/surface.d.ts +5 -0
  147. package/dist/types/packages/desk/src/index.d.ts +13 -0
  148. package/dist/types/packages/desk/src/kind.d.ts +187 -0
  149. package/dist/types/packages/desk/src/kinds/world.d.ts +429 -0
  150. package/dist/types/packages/desk/src/kit/arrays.d.ts +88 -0
  151. package/dist/types/packages/desk/src/kit/blobs.d.ts +40 -0
  152. package/dist/types/packages/desk/src/kit/book.d.ts +2 -0
  153. package/dist/types/packages/desk/src/kit/editor.d.ts +103 -0
  154. package/dist/types/packages/desk/src/kit/eye.d.ts +37 -0
  155. package/dist/types/packages/desk/src/kit/hold.d.ts +1 -0
  156. package/dist/types/packages/desk/src/kit/index.d.ts +26 -0
  157. package/dist/types/packages/desk/src/kit/inside.d.ts +116 -0
  158. package/dist/types/packages/desk/src/kit/layer.d.ts +159 -0
  159. package/dist/types/packages/desk/src/kit/light.d.ts +3 -0
  160. package/dist/types/packages/desk/src/kit/mesh.d.ts +29 -0
  161. package/dist/types/packages/desk/src/kit/mips.d.ts +2 -0
  162. package/dist/types/packages/desk/src/kit/nav.d.ts +2 -0
  163. package/dist/types/packages/desk/src/kit/paper-tex.d.ts +5 -0
  164. package/dist/types/packages/desk/src/kit/physics.d.ts +44 -0
  165. package/dist/types/packages/desk/src/kit/place.d.ts +44 -0
  166. package/dist/types/packages/desk/src/kit/print.d.ts +30 -0
  167. package/dist/types/packages/desk/src/kit/raster.d.ts +34 -0
  168. package/dist/types/packages/desk/src/kit/sdf.d.ts +1 -0
  169. package/dist/types/packages/desk/src/kit/seeds.d.ts +11 -0
  170. package/dist/types/packages/desk/src/kit/services.d.ts +34 -0
  171. package/dist/types/packages/desk/src/kit/springs.d.ts +12 -0
  172. package/dist/types/packages/desk/src/kit/strokes.d.ts +93 -0
  173. package/dist/types/packages/desk/src/kit/text.d.ts +142 -0
  174. package/dist/types/packages/desk/src/kit/uniform.d.ts +8 -0
  175. package/dist/types/packages/desk/src/kit/view.d.ts +39 -0
  176. package/dist/types/packages/desk/src/kit/wgsl.d.ts +15 -0
  177. package/dist/types/packages/desk/src/lattice/line.d.ts +15 -0
  178. package/dist/types/packages/desk/src/lattice/lod.d.ts +52 -0
  179. package/dist/types/packages/desk/src/lattice/ruler.d.ts +88 -0
  180. package/dist/types/packages/desk/src/marks/assemble.d.ts +65 -0
  181. package/dist/types/packages/desk/src/marks/ease.d.ts +4 -0
  182. package/dist/types/packages/desk/src/marks/laser.d.ts +41 -0
  183. package/dist/types/packages/desk/src/marks/layout.d.ts +167 -0
  184. package/dist/types/packages/desk/src/marks/pass.d.ts +59 -0
  185. package/dist/types/packages/desk/src/marks/shaders.d.ts +12 -0
  186. package/dist/types/packages/desk/src/mat/grid.d.ts +50 -0
  187. package/dist/types/packages/desk/src/mat/lamp.d.ts +14 -0
  188. package/dist/types/packages/desk/src/mat/layout.d.ts +144 -0
  189. package/dist/types/packages/desk/src/mat/mat-pass.d.ts +83 -0
  190. package/dist/types/packages/desk/src/mat/night.d.ts +95 -0
  191. package/dist/types/packages/desk/src/mat/projector.d.ts +39 -0
  192. package/dist/types/packages/desk/src/mat/shaders.d.ts +17 -0
  193. package/dist/types/packages/desk/src/mat/tilt.d.ts +14 -0
  194. package/dist/types/packages/desk/src/nav/flight.d.ts +186 -0
  195. package/dist/types/packages/desk/src/nav/portal.d.ts +95 -0
  196. package/dist/types/packages/desk/src/object.d.ts +46 -0
  197. package/dist/types/packages/desk/src/pass-instrument.d.ts +89 -0
  198. package/dist/types/packages/desk/src/shaders.d.ts +12 -0
  199. package/dist/types/packages/desk/src/shaders.gen.d.ts +16 -0
  200. package/dist/types/packages/desk/src/submit-instrument.d.ts +36 -0
  201. package/dist/types/packages/desk/src/theme.d.ts +475 -0
  202. package/dist/types/packages/desk/src/tray/carry.d.ts +99 -0
  203. package/dist/types/packages/desk/src/tray/drawer.d.ts +113 -0
  204. package/dist/types/packages/desk/src/tray/flux.d.ts +46 -0
  205. package/dist/types/packages/desk/src/tray/index.d.ts +4 -0
  206. package/dist/types/packages/desk/src/tray/lattice.d.ts +71 -0
  207. package/dist/types/packages/desk/src/tray/layout.d.ts +7 -0
  208. package/dist/types/packages/desk/src/tray/look.d.ts +29 -0
  209. package/dist/types/packages/desk/src/tray/pass.d.ts +96 -0
  210. package/dist/types/packages/desk/src/tray/shaders.d.ts +10 -0
  211. package/dist/types/packages/desk/src/tray/specimens.d.ts +199 -0
  212. package/dist/types/packages/devtools/src/attach.d.ts +22 -8
  213. package/dist/types/packages/devtools/src/dock.d.ts +18 -14
  214. package/dist/types/packages/devtools/src/gpu-panel.d.ts +105 -0
  215. package/dist/types/packages/devtools/src/index.d.ts +5 -4
  216. package/dist/types/packages/dom/src/desk-host.d.ts +91 -0
  217. package/dist/types/packages/dom/src/host.d.ts +13 -14
  218. package/dist/types/packages/dom/src/index.d.ts +12 -10
  219. package/dist/types/packages/dom/src/loop.d.ts +18 -1
  220. package/dist/types/packages/dom/src/pointer-adapter.d.ts +5 -29
  221. package/dist/types/packages/dom/src/widget-focus.d.ts +17 -13
  222. package/dist/types/packages/ice/src/desk-engine.d.ts +2 -0
  223. package/dist/types/packages/ice/src/desk-kit.d.ts +6 -0
  224. package/dist/types/packages/ice/src/desk-objects.d.ts +5 -0
  225. package/dist/types/packages/ice/src/desk.d.ts +5 -0
  226. package/dist/types/packages/kernel/src/atlas-pack.d.ts +157 -0
  227. package/dist/types/packages/kernel/src/bezier.d.ts +0 -15
  228. package/dist/types/packages/kernel/src/coords.d.ts +0 -39
  229. package/dist/types/packages/kernel/src/index.d.ts +2 -2
  230. package/dist/types/packages/kernel/src/nav-flight.d.ts +6 -0
  231. package/dist/types/packages/kernel/src/tray.d.ts +155 -0
  232. package/dist/types/packages/objects/src/board/board-pass.d.ts +180 -0
  233. package/dist/types/packages/objects/src/board/board-pen.d.ts +36 -0
  234. package/dist/types/packages/objects/src/board/board.d.ts +104 -0
  235. package/dist/types/packages/objects/src/board/data.d.ts +31 -0
  236. package/dist/types/packages/objects/src/board/history.d.ts +33 -0
  237. package/dist/types/packages/objects/src/board/kind.d.ts +208 -0
  238. package/dist/types/packages/objects/src/board/layout.d.ts +102 -0
  239. package/dist/types/packages/objects/src/board/object.d.ts +7 -0
  240. package/dist/types/packages/objects/src/board/pen.d.ts +71 -0
  241. package/dist/types/packages/objects/src/board/shaders.d.ts +36 -0
  242. package/dist/types/packages/objects/src/board/stroke.d.ts +99 -0
  243. package/dist/types/packages/objects/src/board/theme.d.ts +150 -0
  244. package/dist/types/packages/objects/src/calendar/data.d.ts +97 -0
  245. package/dist/types/packages/objects/src/calendar/events.d.ts +58 -0
  246. package/dist/types/packages/objects/src/calendar/hand.d.ts +53 -0
  247. package/dist/types/packages/objects/src/calendar/host/input.d.ts +67 -0
  248. package/dist/types/packages/objects/src/calendar/host/print.d.ts +9 -0
  249. package/dist/types/packages/objects/src/calendar/kind.d.ts +297 -0
  250. package/dist/types/packages/objects/src/calendar/law.d.ts +151 -0
  251. package/dist/types/packages/objects/src/calendar/layout.d.ts +47 -0
  252. package/dist/types/packages/objects/src/calendar/month.d.ts +75 -0
  253. package/dist/types/packages/objects/src/calendar/object.d.ts +45 -0
  254. package/dist/types/packages/objects/src/calendar/pad.d.ts +42 -0
  255. package/dist/types/packages/objects/src/calendar/pass.d.ts +229 -0
  256. package/dist/types/packages/objects/src/calendar/print.d.ts +87 -0
  257. package/dist/types/packages/objects/src/calendar/printing.d.ts +80 -0
  258. package/dist/types/packages/objects/src/calendar/roll.d.ts +45 -0
  259. package/dist/types/packages/objects/src/calendar/shaders.d.ts +12 -0
  260. package/dist/types/packages/objects/src/calendar/sheet.d.ts +43 -0
  261. package/dist/types/packages/objects/src/calendar/tiles.d.ts +67 -0
  262. package/dist/types/packages/objects/src/calendar/turn.d.ts +73 -0
  263. package/dist/types/packages/objects/src/calendar/writing.d.ts +42 -0
  264. package/dist/types/packages/objects/src/index.d.ts +22 -0
  265. package/dist/types/packages/objects/src/kinds.d.ts +24 -0
  266. package/dist/types/packages/objects/src/minimat/inside.d.ts +17 -0
  267. package/dist/types/packages/objects/src/minimat/kind.d.ts +51 -0
  268. package/dist/types/packages/objects/src/minimat/layout.d.ts +83 -0
  269. package/dist/types/packages/objects/src/minimat/minimat.d.ts +179 -0
  270. package/dist/types/packages/objects/src/minimat/object.d.ts +8 -0
  271. package/dist/types/packages/objects/src/minimat/pass.d.ts +61 -0
  272. package/dist/types/packages/objects/src/minimat/shaders.d.ts +10 -0
  273. package/dist/types/packages/objects/src/minimat/theme.d.ts +98 -0
  274. package/dist/types/packages/objects/src/notebook/ink.d.ts +70 -0
  275. package/dist/types/packages/objects/src/notebook/kind.d.ts +236 -0
  276. package/dist/types/packages/objects/src/notebook/law.d.ts +129 -0
  277. package/dist/types/packages/objects/src/notebook/layout.d.ts +46 -0
  278. package/dist/types/packages/objects/src/notebook/leaf.d.ts +44 -0
  279. package/dist/types/packages/objects/src/notebook/mesh.d.ts +20 -0
  280. package/dist/types/packages/objects/src/notebook/motion.d.ts +107 -0
  281. package/dist/types/packages/objects/src/notebook/object.d.ts +8 -0
  282. package/dist/types/packages/objects/src/notebook/pages.d.ts +56 -0
  283. package/dist/types/packages/objects/src/notebook/pass.d.ts +197 -0
  284. package/dist/types/packages/objects/src/notebook/pick.d.ts +30 -0
  285. package/dist/types/packages/objects/src/notebook/raster.d.ts +28 -0
  286. package/dist/types/packages/objects/src/notebook/shaders.d.ts +11 -0
  287. package/dist/types/packages/objects/src/notebook/shape.d.ts +155 -0
  288. package/dist/types/packages/objects/src/notebook/theme.d.ts +76 -0
  289. package/dist/types/packages/objects/src/palette.d.ts +577 -0
  290. package/dist/types/packages/objects/src/paper/host/body.d.ts +30 -0
  291. package/dist/types/packages/objects/src/paper/kind.d.ts +81 -0
  292. package/dist/types/packages/objects/src/paper/layout.d.ts +73 -0
  293. package/dist/types/packages/objects/src/paper/object.d.ts +31 -0
  294. package/dist/types/packages/objects/src/paper/pages.d.ts +43 -0
  295. package/dist/types/packages/objects/src/paper/paper-pass.d.ts +86 -0
  296. package/dist/types/packages/objects/src/paper/paper.d.ts +117 -0
  297. package/dist/types/packages/objects/src/paper/shaders.d.ts +18 -0
  298. package/dist/types/packages/objects/src/paper/theme.d.ts +58 -0
  299. package/dist/types/packages/objects/src/paper/typing.d.ts +51 -0
  300. package/dist/types/packages/objects/src/paper/writing.d.ts +161 -0
  301. package/dist/types/packages/objects/src/photo/carry.d.ts +33 -0
  302. package/dist/types/packages/objects/src/photo/kind.d.ts +136 -0
  303. package/dist/types/packages/objects/src/photo/layout.d.ts +39 -0
  304. package/dist/types/packages/objects/src/photo/object.d.ts +5 -0
  305. package/dist/types/packages/objects/src/photo/photo-pass.d.ts +104 -0
  306. package/dist/types/packages/objects/src/photo/photo.d.ts +262 -0
  307. package/dist/types/packages/objects/src/photo/pictures.d.ts +152 -0
  308. package/dist/types/packages/objects/src/photo/sample.d.ts +9 -0
  309. package/dist/types/packages/objects/src/photo/shaders.d.ts +18 -0
  310. package/dist/types/packages/objects/src/preset.d.ts +44 -0
  311. package/dist/types/packages/objects/src/shaders.d.ts +3 -0
  312. package/dist/types/packages/objects/src/shaders.gen.d.ts +21 -0
  313. package/dist/types/packages/react/src/canvas-hooks.d.ts +12 -0
  314. package/dist/types/packages/react/src/desk.d.ts +56 -0
  315. package/dist/types/packages/react/src/engine-context.d.ts +2 -2
  316. package/dist/types/packages/react/src/frame-preview-boundary.d.ts +19 -0
  317. package/dist/types/packages/react/src/hooks.d.ts +9 -14
  318. package/dist/types/packages/react/src/index.d.ts +16 -9
  319. package/dist/types/packages/react/src/keymap.d.ts +21 -2
  320. package/dist/types/packages/react/src/selection-menu.d.ts +171 -0
  321. package/dist/types/packages/react/src/tray-bar.d.ts +124 -0
  322. package/package.json +64 -31
  323. package/dist/chunk-2TNVB2ON.js +0 -1163
  324. package/dist/chunk-2TNVB2ON.js.map +0 -1
  325. package/dist/chunk-OR5FNVUE.js.map +0 -1
  326. package/dist/chunk-Q665T4L7.js +0 -681
  327. package/dist/chunk-Q665T4L7.js.map +0 -1
  328. package/dist/chunk-VGVDJZSP.js.map +0 -1
  329. package/dist/ground.js +0 -1108
  330. package/dist/ground.js.map +0 -1
  331. package/dist/r3f.js +0 -1644
  332. package/dist/r3f.js.map +0 -1
  333. package/dist/types/packages/core/src/input/measure-queue.d.ts +0 -30
  334. package/dist/types/packages/core/src/settings/ground-config.d.ts +0 -101
  335. package/dist/types/packages/core/src/systems/measure-ingest.d.ts +0 -26
  336. package/dist/types/packages/devtools/src/gl-panel.d.ts +0 -69
  337. package/dist/types/packages/dom/src/measure-adapter.d.ts +0 -31
  338. package/dist/types/packages/dom/src/measure-wiring.d.ts +0 -49
  339. package/dist/types/packages/dom/src/planes.d.ts +0 -33
  340. package/dist/types/packages/dom/src/reflectors/chrome.d.ts +0 -33
  341. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +0 -59
  342. package/dist/types/packages/dom/src/reflectors/graybox.d.ts +0 -31
  343. package/dist/types/packages/dom/src/reflectors/plane-transform.d.ts +0 -38
  344. package/dist/types/packages/ground/src/index.d.ts +0 -69
  345. package/dist/types/packages/ground/src/layer.d.ts +0 -40
  346. package/dist/types/packages/ground/src/pass.d.ts +0 -41
  347. package/dist/types/packages/ground/src/passes/grid-contract.d.ts +0 -28
  348. package/dist/types/packages/ground/src/passes/grid-magnet-pass.d.ts +0 -2
  349. package/dist/types/packages/ground/src/passes/grid-magnet.d.ts +0 -31
  350. package/dist/types/packages/ground/src/passes/grid.d.ts +0 -10
  351. package/dist/types/packages/ground/src/passes/guides-collect.d.ts +0 -14
  352. package/dist/types/packages/ground/src/passes/guides.d.ts +0 -3
  353. package/dist/types/packages/ground/src/passes/magnet-collect.d.ts +0 -63
  354. package/dist/types/packages/ground/src/passes/soup-collect.d.ts +0 -35
  355. package/dist/types/packages/ground/src/passes/soup-mesh.d.ts +0 -8
  356. package/dist/types/packages/ground/src/passes/wires-collect.d.ts +0 -35
  357. package/dist/types/packages/ground/src/passes/wires.d.ts +0 -10
  358. package/dist/types/packages/ground/src/poles.d.ts +0 -47
  359. package/dist/types/packages/ground/src/renderer.d.ts +0 -15
  360. package/dist/types/packages/ice/src/ground.d.ts +0 -1
  361. package/dist/types/packages/ice/src/r3f.d.ts +0 -1
  362. package/dist/types/packages/kernel/src/eviction.d.ts +0 -23
  363. package/dist/types/packages/kernel/src/zoom-bands.d.ts +0 -19
  364. package/dist/types/packages/r3f/src/bridge.d.ts +0 -129
  365. package/dist/types/packages/r3f/src/composite-material.d.ts +0 -26
  366. package/dist/types/packages/r3f/src/compositor-pass.d.ts +0 -105
  367. package/dist/types/packages/r3f/src/dev-write-trap.d.ts +0 -30
  368. package/dist/types/packages/r3f/src/gl-root.d.ts +0 -96
  369. package/dist/types/packages/r3f/src/gl-router.d.ts +0 -70
  370. package/dist/types/packages/r3f/src/index.d.ts +0 -17
  371. package/dist/types/packages/r3f/src/island-state.d.ts +0 -112
  372. package/dist/types/packages/r3f/src/island.d.ts +0 -13
  373. package/dist/types/packages/r3f/src/pool.d.ts +0 -56
  374. package/dist/types/packages/r3f/src/preview-capture.d.ts +0 -19
  375. package/dist/types/packages/r3f/src/resource-registry.d.ts +0 -30
  376. package/dist/types/packages/r3f/src/self-sustain.d.ts +0 -39
  377. package/dist/types/packages/r3f/src/use-island-frame.d.ts +0 -39
  378. package/dist/types/packages/react/src/infinite-canvas.d.ts +0 -140
  379. package/dist/types/packages/react/src/preview-snapshots.d.ts +0 -17
  380. package/dist/types/packages/react/src/widget-preview.d.ts +0 -14
  381. package/dist/types/packages/react/src/widget-root.d.ts +0 -33
@@ -0,0 +1,152 @@
1
+ import type { RasterBudget } from "../../../desk/src/index";
2
+ import { type DecodedPicture } from "../../../desk/src/kit/index";
3
+ /** The thumbnail array's layer side, texels: a picture's chain from its first level that fits (a print up to THUMB device px wide samples nothing finer). */
4
+ export declare const THUMB = 512;
5
+ export declare const THUMB_MIPS: number;
6
+ /** The pool: how many details one frame binds (the prints larger on screen than the thumbnail serves, largest first). */
7
+ export declare const DETAIL_SLOTS = 8;
8
+ /**
9
+ * A detail is asked this far (levels) BEFORE the print needs it — once it shows its picture larger than THUMB / √2 device px —
10
+ * so it lands while both tiers are still the same texels and the swap shows nothing (a zoom from there to THUMB px is 1.41×).
11
+ */
12
+ export declare const HEADROOM = 0.5;
13
+ /** Details fetched at once (each decodes its picture again). */
14
+ export declare const BUILDS = 2;
15
+ /** How a picture's level 0 comes back: raw sRGB rgba8 bytes, or a decoded source the device copies; undefined when it cannot. */
16
+ export type PictureSource = () => Promise<DecodedPicture | undefined>;
17
+ /** The finer cut of a picture's chain a print large on screen samples: its own texture from level `base` down. */
18
+ export interface Detail {
19
+ readonly texture: GPUTexture;
20
+ readonly view: GPUTextureView;
21
+ readonly base: number;
22
+ readonly bytes: number;
23
+ }
24
+ /** A picture on the device: its thumbnail's layer, the chain level that layer starts at, the detail and the slot a frame binds it in. */
25
+ export interface Picture {
26
+ readonly id: number;
27
+ readonly width: number;
28
+ readonly height: number;
29
+ readonly mips: number;
30
+ /** Its thumbnail's layer in the shared array (−1 once dropped). */
31
+ layer: number;
32
+ /** The chain's level the thumbnail's level 0 is (0: the whole picture fits the layer). */
33
+ readonly base: number;
34
+ detail: Detail | null;
35
+ /** The pool slot binding the detail this frame; −1: the thumbnail serves. */
36
+ slot: number;
37
+ /** Turns over whenever the slot or the detail moves — a print's record folds it in (`aux`) and is packed again. */
38
+ tier: number;
39
+ }
40
+ /** The level of a `w × h` picture's chain the thumbnail starts at: the first whose both sides fit `side` (the kit's `tailBase`). */
41
+ export declare const thumbBase: (w: number, h: number, side?: number) => number;
42
+ /**
43
+ * The lod a print samples its picture at (photo.wgsl `photo_picture`): texels per device pixel along the picture's wider axis,
44
+ * log2 — `inner` the picture's half extents on the sheet (world), `zoom · dpr` device pixels a world unit.
45
+ */
46
+ export declare function pictureLod(width: number, height: number, inner: readonly [number, number], zoom: number, dpr: number): number;
47
+ /** The detail a print sampling at `lod` needs: the finest level it samples, below the thumbnail's `base` — null when the thumbnail serves (or will within HEADROOM). */
48
+ export declare function detailNeed(lod: number, base: number): number | null;
49
+ export { chainBytes, Layers } from "../../../desk/src/kit/index";
50
+ /** The group-1 layout every print pipeline binds: the thumbnail array, then the pool's DETAIL_SLOTS details. */
51
+ export declare function picturesLayout(device: GPUDevice): GPUBindGroupLayout;
52
+ export interface PictureStats {
53
+ readonly pictures: number;
54
+ /** Thumbnail layers in use, and the array's capacity. */
55
+ readonly layers: number;
56
+ readonly capacity: number;
57
+ readonly details: number;
58
+ readonly slotted: number;
59
+ readonly building: number;
60
+ /** Detail builds landed, detail cuts made, details the budget evicted — since the store was made. */
61
+ readonly built: number;
62
+ readonly cut: number;
63
+ readonly evicted: number;
64
+ readonly bytes: {
65
+ readonly thumbnails: number;
66
+ readonly details: number;
67
+ };
68
+ }
69
+ /** The pictures of one device: the thumbnail array, the details, the pool — one per photo pass family (every slot shares it). */
70
+ export declare class PictureStore {
71
+ readonly layout: GPUBindGroupLayout;
72
+ /** The group every slot binds at 1 this frame: the array and the pool (blank where a slot is empty). */
73
+ group: GPUBindGroup;
74
+ /** A detail landed or went (async): the kind wakes the desk so the next step binds it. */
75
+ onReady: () => void;
76
+ private readonly device;
77
+ /** The thumbnails: a layer a picture (the kit's shared array). */
78
+ private readonly thumbs;
79
+ private readonly blank;
80
+ private readonly blankArray;
81
+ private readonly pictures;
82
+ private readonly slots;
83
+ private readonly asked;
84
+ private budget;
85
+ private nextId;
86
+ private inflight;
87
+ private built;
88
+ private cut;
89
+ private evicted;
90
+ constructor(device: GPUDevice);
91
+ /** Charge the desk's budget from now on (the kind's host lends it; the oracle's bare pass has none). */
92
+ attach(budget: RasterBudget | undefined): void;
93
+ /** The budget's ask: the thumbnails always (they RESIDE since K9 R2 — never in the LRU — so this is their word if ever asked); a detail while a slot binds it. */
94
+ keeps(key: string): boolean;
95
+ /**
96
+ * A picture from its level 0 — raw bytes written, or a decoded source copied — and how to get that level 0 again (a detail's
97
+ * fetch): its chain made, its tail copied into a thumbnail layer, the chain let go. Null when the array holds no more layers.
98
+ */
99
+ make(first: DecodedPicture, source: PictureSource): Picture | null;
100
+ /** A picture no print names any more: its layer back to the array, its detail destroyed, its slot freed. */
101
+ drop(pic: Picture): void;
102
+ /** Asks the next `step` has to answer — what a frame drew since the last step (K7a: the kind is due until it answers them). */
103
+ pending(): boolean;
104
+ /** A print asked for its picture at `lod` this frame (a slot's prepare). */
105
+ ask(pic: Picture, lod: number): void;
106
+ /**
107
+ * K9 R4 — the HAND's prepare: every picture a slot binds is asked again at the lod it was granted, so the step keeps it. The
108
+ * desk copy behind a carried print is prepared once per stamp and its prints ask nothing on the frames between; the hand's
109
+ * frame is the only prepare then, and a step that saw its ask alone freed every other slot — a one-frame softness pop at the
110
+ * put-down (the rest frame drew them from their thumbnails, the tick re-slotted them), and while un-slotted `keeps` was false,
111
+ * so over the cap `trim` could evict a detail on screen behind the hand.
112
+ */
113
+ keepSlotted(): void;
114
+ /**
115
+ * THE FRAME BOUNDARY (the kind's tick, before the next frame's build): the pictures asked since the last step, largest on
116
+ * screen first — the first DETAIL_SLOTS that need more than the thumbnail (or will within HEADROOM) keep or take a slot
117
+ * once their detail is resident; a missing or too-coarse detail is fetched (a coarser one binds meanwhile), a detail finer
118
+ * than its print samples now is cut down; every other slot is freed. Returns whether a print's tier moved (its record must be
119
+ * packed again, the desk redrawn). O(1) when nothing was drawn since the last step.
120
+ */
121
+ step(): boolean;
122
+ stats(): PictureStats;
123
+ dispose(): void;
124
+ /** K7b: the thumbnail array as the flat-card pipeline binds it — its view, made again when the array grew (its version moved). */
125
+ thumbView(): {
126
+ readonly version: number;
127
+ readonly view: GPUTextureView;
128
+ };
129
+ private thumbCard;
130
+ private bind;
131
+ private thumbsBytes;
132
+ /** The thumbnails RESIDE in the budget (K9 R2): never evicted, outside the caches' room, charged at what the layers in use weigh. */
133
+ private chargeThumbs;
134
+ private chargeDetail;
135
+ /** A picture's whole chain from its level 0 (as it always was made: written or copied, then mips.ts). */
136
+ private chain;
137
+ /** The levels of `from` (a chain from level `fromBase`) from level `base` down, in a texture of their own — a device copy. */
138
+ private cutChain;
139
+ /** Replace `p`'s detail (null: none): the old destroyed and released, the new charged; a slot binding it is rebound. */
140
+ private setDetail;
141
+ /**
142
+ * The finest level from `base` whose chain the budget's ROOM holds beside the other bound details — so the details alone never
143
+ * hold more than the caches' room (K9 R2: the thumbnails reside outside it and pin nothing here; before, they were counted and
144
+ * past ~128 pictures no detail could ever be afforded); null when not even the coarsest detail fits (the thumbnail serves, a
145
+ * little soft). The other owners' rasters are the budget's LRU to trim, as before.
146
+ */
147
+ private afford;
148
+ /** Fetch `p`'s level 0 again and make its detail from level `base` — or the finest the budget affords — unless one that fine is on its way, or BUILDS are. */
149
+ private fetch;
150
+ /** `p`'s detail cut down to level `base` (it zoomed out): a copy of what it samples, the finer levels let go. */
151
+ private cutTo;
152
+ }
@@ -0,0 +1,9 @@
1
+ /** The sample's name — a print whose `blob` is this shows the sample (its `width` × `height` the sample's 3 : 2). */
2
+ export declare const SAMPLE_PICTURE = "sample:dusk";
3
+ /** The sample's size, texels (a 6 × 4's aspect — the print's extent at rest). */
4
+ export declare const SAMPLE_SIZE: {
5
+ readonly w: 240;
6
+ readonly h: 160;
7
+ };
8
+ /** The sample at `width` × `height` texels: sRGB rgba8, row-major from the top. */
9
+ export declare function samplePicture(width?: number, height?: number): Uint8Array<ArrayBuffer>;
@@ -0,0 +1,18 @@
1
+ import type { CardMaterial } from "../../../desk/src/index";
2
+ import type { ComposeOptions } from "../../../desk/src/engine/index";
3
+ import { type ShaderText } from "../../../desk/src/kit/index";
4
+ /** The print's own shader files (the kit's pieces come by name). */
5
+ export declare const PHOTO_SHADER_FILES: {
6
+ readonly photo: "photo/photo.wgsl";
7
+ readonly photoCard: "photo/photo-card.wgsl";
8
+ readonly photoPass: "photo/photo-pass.wgsl";
9
+ };
10
+ /** The photo pass's program: the kit's view · portal · sdf · light, then the print's records, module and entry. */
11
+ export type PhotoShaders = ComposeOptions;
12
+ export declare function photoShaders(text: ShaderText): PhotoShaders;
13
+ /**
14
+ * The print's CARD MATERIAL (K7b, design-016 §6 K7): the flat-card pipeline draws a print with photo-card.wgsl's quad and
15
+ * fragment over the photo pass's own records, knobs, samplers and the thumbnail array (`PhotoPass.cardResources`, in this
16
+ * order) — every print whose picture has no detail bound (the card binds no pool: its `photo_texel` is the thumbnail's).
17
+ */
18
+ export declare function photoCard(text: ShaderText): CardMaterial;
@@ -0,0 +1,44 @@
1
+ import { type Tool } from "../../core/src/index";
2
+ /** The desk's reference objects, in the order an app registers them. */
3
+ export declare const DESK_OBJECTS: readonly [import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType];
4
+ /**
5
+ * The desk's select tool: the bare mat pans, shift-drag draws the vellum marquee; a drag on an object moves it. It answers `v` — core's
6
+ * select's letter — since core's select is compiled but never legal on the desk, and the keymap binds a letter only to a tool legal in
7
+ * the current canvas (K9 S8: `v` threw on the desk, and `h` left it in pan with no letter back).
8
+ */
9
+ export declare const deskSelect: Tool;
10
+ /** The tools the engine compiles — a typed engine must name `select` even when no canvas allows it. */
11
+ export declare const DESK_TOOLS: readonly Tool[];
12
+ /**
13
+ * The desk as a canvas type: what it places, its two tools, the natural arrival band (FIT: pad 80, zoom ½–1). It places every object
14
+ * that PROVIDES `DESK_OBJECT` (K5b's D-K5b.2, the SDK's key since K8a — the six declare it as a plugin kind does) and names no type: no
15
+ * list here names a kind.
16
+ */
17
+ export declare const DeskCanvas: import("../../core/src/index").CanvasType;
18
+ /**
19
+ * THE PRESET — `createCanvasEngine(DESK_ENGINE)` is the desk the README describes: the six reference objects, the desk's
20
+ * tools with `desk.select` in hand, the desk canvas as the root, the scale-free zoom (design-015 §9: `ZOOM_MIN`–`ZOOM_MAX`),
21
+ * the plain wheel zooming about the pointer, and the zoom-through ON (a wheel that leaves a face covering the view cuts into it).
22
+ * Spread it to add to it: `createCanvasEngine({ ...DESK_ENGINE, widgets: [...DESK_OBJECTS, Mine] })`.
23
+ */
24
+ export declare const DESK_ENGINE: {
25
+ readonly widgets: readonly [import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType, import("../../core/src/index").WidgetType];
26
+ readonly tools: readonly Tool[];
27
+ readonly canvasTypes: readonly [import("../../core/src/index").CanvasType];
28
+ readonly rootCanvas: import("../../core/src/index").CanvasType;
29
+ readonly presentationFallback: import("../../core/src/index").CanvasType;
30
+ readonly settings: {
31
+ readonly zoom: {
32
+ readonly min: 1e-8;
33
+ readonly max: 100000000;
34
+ };
35
+ readonly gestures: {
36
+ readonly wheel: "zoom";
37
+ };
38
+ readonly nav: {
39
+ readonly zoomThrough: {
40
+ readonly enabled: true;
41
+ };
42
+ };
43
+ };
44
+ };
@@ -0,0 +1,3 @@
1
+ import { type ShaderText } from "../../desk/src/index";
2
+ /** The text of every file a shader-file map names, by the map's keys: a kind's file from this package, the kit's from the desk. */
3
+ export declare const shaderText: ShaderText;
@@ -0,0 +1,21 @@
1
+ export declare const WGSL: {
2
+ readonly "board/board-card.wgsl": "// The whiteboard as the board pass AND the flat-card pipeline draw it (K7b, design-016 §6 K7): the corner's clip position and\n// the THUMBNAIL's ink, over the board's bindings by their own names — `boards`, `board_k`, `board_thumbs` … — beside the slot's\n// `u`, `gobo_tex`, `noise_tex`, which whichever entry composes them declares. (Its fragment is the pass entry's `fs`, written\n// out — the card splices the same statements, shaders.ts `BOARD_FRAGMENT`.) `shade_board` reads its ink\n// through `board_ink` and the live board's layers through `board_stroke` / `board_wet`: the pass's entry reads the pool and\n// the live layers there, else the thumbnail; the card binds neither and reads the thumbnail alone (a board with a raster, or\n// being written on, is the pass's to draw).\n\n// The thumbnails' layer side (board-pass.ts `BOARD_THUMB`).\nconst BOARD_THUMB = 512.0;\n\nconst BD_CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0),\n);\n\n// A board's ink at (uv, lod) from its THUMBNAIL — lod in its raster's chain: the layer (`tier.x`), which starts at the level k of\n// its source's chain (`tier.y`) — the chain's tail at the layer's origin, its edge carried to the layer's — at lod − k.\nfn board_thumb(B: Board, uv: vec2f, lod: f32) -> vec4f {\n let k = B.tier.y;\n let size = max(floor(B.texels / exp2(k)), vec2f(1.0));\n return textureSampleLevel(board_thumbs, board_ink_samp, uv * size / BOARD_THUMB, i32(B.tier.x), max(lod - k, 0.0));\n}\n\nfn board_quad(slot: u32, vid: u32) -> vec4f {\n let B = boards[slot];\n let zoom = max(u.cam.z, 1.0e-12);\n let lo = (B.quad.xy - u.cam.xy) * zoom; // CSS px\n let hi = (B.quad.zw - u.cam.xy) * zoom;\n if (hi.x < u.box.x || hi.y < u.box.y || lo.x > u.box.x + u.box.z || lo.y > u.box.y + u.box.w) { return vec4f(2.0, 2.0, 2.0, 1.0); } // degenerate: a culled board collapses\n let corner = BD_CORNERS[vid];\n let pos = mix(lo, hi, corner * 0.5 + 0.5);\n return vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n}\n";
3
+ readonly "board/board-pass.wgsl": "// The board pass — the whiteboards: one quad per board (its extent the record's own: the slab,\n// its shadow, the tool in the hand — resolved on the CPU), one `shade_board` per covered\n// fragment, premultiplied output over the mat. Group 0 is the slot's (the mat's block, the\n// knobs, the records, the gobo, the noise, the samplers); group 1 is the SAME for every board\n// (K6a, K-L4 — a run of boards is one instanced draw): the far-LOD thumbnails' array, the pool's\n// rasters' ink (with mips), the live board's stroke and wet layers; a board's record says where\n// its ink is (`tier`). The camera, the box and the portal chain come through the mat's block, as\n// for the note and the notebook. The quad, the fragment and the thumbnail's ink are\n// board-card.wgsl's — the flat-card pipeline draws a board with the same three (K7b).\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> board_k: BoardUniforms;\n@group(0) @binding(2) var<storage, read> boards: array<Board>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var board_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var board_noise_samp: sampler;\n@group(0) @binding(7) var board_ink_samp: sampler; // trilinear — the ink seen small reads its mips\n@group(0) @binding(8) var board_lin_samp: sampler; // bilinear, level 0 — the stroke and the wet layer\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n@group(1) @binding(0) var board_thumbs: texture_2d_array<f32>; // every board's ink chain from its first level ≤ BOARD_THUMB², a layer each\n@group(1) @binding(1) var ink0: texture_2d<f32>; // the pool: a board's raster — premultiplied linear ink, coverage in alpha\n@group(1) @binding(2) var ink1: texture_2d<f32>;\n@group(1) @binding(3) var ink2: texture_2d<f32>;\n@group(1) @binding(4) var ink3: texture_2d<f32>;\n@group(1) @binding(5) var ink4: texture_2d<f32>;\n@group(1) @binding(6) var ink5: texture_2d<f32>;\n@group(1) @binding(7) var ink6: texture_2d<f32>;\n@group(1) @binding(8) var ink7: texture_2d<f32>;\n@group(1) @binding(9) var stroke_live: texture_2d<f32>; // the live board's stroke being laid (r8)\n@group(1) @binding(10) var wet_live: texture_2d<f32>; // …and how wet its ink still is (r8)\n\n// A board's ink at (uv, lod) — lod in its raster's chain. `tier`: its thumbnail's layer, the level of its source's chain the\n// layer starts at (k), its pool slot (−1: none), live. The slot's raster, else the layer (board-card.wgsl `board_thumb`).\nfn board_ink(B: Board, uv: vec2f, lod: f32) -> vec4f {\n switch i32(B.tier.z) {\n case 0: { return textureSampleLevel(ink0, board_ink_samp, uv, lod); }\n case 1: { return textureSampleLevel(ink1, board_ink_samp, uv, lod); }\n case 2: { return textureSampleLevel(ink2, board_ink_samp, uv, lod); }\n case 3: { return textureSampleLevel(ink3, board_ink_samp, uv, lod); }\n case 4: { return textureSampleLevel(ink4, board_ink_samp, uv, lod); }\n case 5: { return textureSampleLevel(ink5, board_ink_samp, uv, lod); }\n case 6: { return textureSampleLevel(ink6, board_ink_samp, uv, lod); }\n case 7: { return textureSampleLevel(ink7, board_ink_samp, uv, lod); }\n default: {}\n }\n return board_thumb(B, uv, lod);\n}\n\n// The live board's stroke being laid, and how wet its ink is — 0 on any other board (their layers are not bound).\nfn board_stroke(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(stroke_live, board_lin_samp, uv, 0.0).r;\n}\nfn board_wet(B: Board, uv: vec2f) -> f32 {\n if (B.tier.w < 0.5) { return 0.0; }\n return textureSampleLevel(wet_live, board_lin_samp, uv, 0.0).r;\n}\n\nstruct BoardOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> BoardOut {\n var out: BoardOut;\n out.idx = order[iid];\n out.clip = board_quad(out.idx, vid);\n return out;\n}\n\n// The fragment, written out: shade_board reached through one more function compiles to one LSB off on a pixel or two a scene\n// (K7b, measured) — so the flat-card pipeline splices THESE statements (shaders.ts `BOARD_FRAGMENT`, a test holds them equal).\n@fragment\nfn fs(in: BoardOut) -> @location(0) vec4f {\n let B = boards[in.idx];\n let zoom = max(u.cam.z, 1.0e-12);\n let dpr = max(u.cam.w, 1.0);\n let px = 1.0 / (zoom * dpr); // world per device px\n let css = 1.0 / zoom; // world per CSS px\n let p = in.clip.xy * px + u.cam.xy; // device px → world\n let c = shade_board(B, u, board_k, p, px, css, in.clip.xy, gobo_tex, board_gobo_samp, noise_tex, board_noise_samp);\n if (c.a < 0.002) { discard; }\n // Premultiplied: the presentation's opacity through the portal clip scales every channel.\n return c * (u.view.w * portal_cover(in.clip.xy / dpr, u.portals, u.clips, dpr));\n}\n";
4
+ readonly "board/board.wgsl": "// THE WHITEBOARD (BOARD.md) — a desk dry-erase board under the mat's one lamp. A PURE module:\n// the record `Board`, the pass's knobs `BoardUniforms`, the mat's block `MatUniforms` and the\n// textures arrive as parameters; it names no colour.\n//\n// The board is a slab on the mat: an aluminium frame round a melamine face recessed a hair\n// below it. The camera is top-down, so the depth is the lamp's work, as for the note and the\n// notebook: the slab casts its footprint along the lamp's ground slope from every height it\n// spans, each blurred by its own height, with a contact shadow at its base; the frame's\n// profile rolls at both rims — turned toward the lamp it brightens and glints, turned away it\n// falls into shade; the frame's lip shades the melamine on the lamp's side. The melamine\n// carries the ink raster — a filter laid into its albedo before the light, so the dapple\n// crosses the drawing as it crosses the mat — its waviness, the ghost of old erasures, fresh\n// ink's wet gloss, and a SHEEN: the window the lamp shines through, mirrored in the glossy face\n// and seen by an eye over the board, so it lands on one part of it and slides as the pointer\n// moves (the research whiteboard's room, reduced to the one bright thing a desk has over it).\n// On the board lies a capped marker; while it is open, the tool in the hand is drawn at the\n// pointer — its shadow meets its tip as it presses. The colour chain is the note's: the\n// reference's grade on the sRGB byte by day, the eye's chain on its linear value by night.\n\nfn bd_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\nfn bd_line(d: f32, w: f32, px: f32) -> f32 { return bd_cov(d, px) - bd_cov(d + w, px); }\nfn bd_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage — a shadow, straight out of the field.\nfn bd_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * bd_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n// Premultiplied \"src over dst\".\nfn bd_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n// A world point raised `h` world units off the desk, in the projector's frame (the note's `paper_desk_at`).\nfn bd_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\nfn bd_encode(c: vec3f) -> vec3f {\n let x = clamp(c, vec3f(0.0), vec3f(1.0));\n return mix(x * 12.92, 1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), x));\n}\n\n// The chain, as the note's `paper_colour`: by day the reference's grade on the sRGB albedo — lit,\n// the identity, so a configured byte is the drawn byte — or, at `chain` 1, through the mat's own\n// double gamma; by night the eye's chain on the linear albedo, as the mat's; between, the cross-fade.\nfn bd_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32, chain: f32) -> vec3f {\n let day = shade_mat(u, mix(albedo, srgb_to_linear(albedo), chain), 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// The lamp on a face (the notebook's `bk_lambert`): its fill, plus the lambert normalised so a\n// flat face reads 1 and capped so a face turned toward a low lamp brightens only so far.\nfn bd_lambert(n: vec3f, L: vec3f, k: BoardUniforms) -> f32 {\n let d = max(dot(n, L), 0.0) / max(L.z, 0.05);\n return k.light.x + (1.0 - k.light.x) * min(d, k.light.y);\n}\n\n// A glint: the lamp mirrored by a face toward the eye straight above (Blinn's half vector).\nfn bd_glint(n: vec3f, L: vec3f, power: f32) -> f32 {\n return pow(max(dot(n, normalize(L + vec3f(0.0, 0.0, 1.0))), 0.0), power);\n}\n\n// How bright a glint can be under the light in force: the Sun's, or the Moon's much less.\nfn bd_lit(u: MatUniforms, k: BoardUniforms) -> f32 { return mix(1.0, k.light.w, clamp(u.night.x, 0.0, 1.0)); }\n\n// A rounded box's outward gradient, by central differences.\nfn bd_grad(q: vec2f, half: vec2f, r: f32) -> vec2f {\n let e = 0.35;\n let g = vec2f(\n sdf_round_box(q + vec2f(e, 0.0), half, r) - sdf_round_box(q - vec2f(e, 0.0), half, r),\n sdf_round_box(q + vec2f(0.0, e), half, r) - sdf_round_box(q - vec2f(0.0, e), half, r));\n let l = length(g);\n return select(vec2f(0.0, -1.0), g / l, l > 1.0e-5);\n}\n\n// ---- the shadows\n\n// A SLAB (a rounded box, `q` in its frame) whose underside is `bottom` and top `top` above the\n// receiver: its footprint cast along the ground slope `S` from every height it spans — each\n// blurred by its own height, the penumbra widening as the slab rises — the union as the max;\n// with the contact shadow at its base while it rests, fading as it floats off. `grow` scales the\n// penumbra's growth (the hand's tools cast sharper); `steps` samples the sweep — enough that the\n// copies never show as steps under that penumbra.\nfn bd_slab(q: vec2f, half: vec2f, r: f32, bottom: f32, top: f32, S: vec2f, k: BoardUniforms, grow: f32, steps: i32) -> f32 {\n var sh = 0.0;\n for (var i = 0; i < steps; i++) {\n let h = mix(bottom, top, f32(i) / f32(steps - 1));\n if (h <= 0.0) { continue; }\n sh = max(sh, bd_blur(sdf_round_box(q - S * h, half, r), k.shadow.x + k.shadow.y * grow * h));\n }\n sh = sh * k.shadow.z;\n let g = max(bottom, 0.0);\n let con = bd_blur(sdf_round_box(q - S * min(g, 1.0) * 0.5, half, r), k.shadow2.x) * k.shadow.w * exp(-g / max(k.shadow2.y, 1.0e-3));\n return max(sh, con);\n}\n\n// ---- the marker\n\n// The tools in the hand cast in the sun's own sharpness — their penumbra grows at this share of the\n// desk objects' rate, their umbra this much firmer — so the shadow of a hovering nib is a clear mark\n// that slides to meet it as it presses: the one cue a hand draws by.\nconst HAND_PENUMBRA = 0.35;\nconst HAND_UMBRA = 1.35;\n\n// THE PEN seen from above, in its own frame: `m` = (u along it from the nib, w across). Its cap\n// slides with `cap`: 0 = on the nib (the marker lying capped), 1 = posted on its back (in the\n// hand), and between, travelling along the barrel as a hand uncaps and posts it. Its ends are\n// ROUND — a domed cap, a round nib, a chamfered plug — so it reads as a moulded thing, not a\n// cut-out. Returns its signed distance, the part the point is on (0 nib · 1 holder · 2 barrel ·\n// 3 stripe · 4 cap · 5 end · 6 the cap's clip · 7 the cap's rim), the local radius, and where\n// the point is on a rounded end (−1 … 0 toward the nib, 0 … 1 toward the back; 0 along the tube).\nfn bd_pen(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> vec4f {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let u = m.x;\n let w = abs(m.y);\n let nr = max(nib, 0.6);\n let c0 = mix(-2.0, L - capL * 0.55, cap); // the cap: from … to\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n var r = R;\n var part = 2.0;\n if (u < 3.2) { r = nr; part = 0.0; }\n else if (u < 14.0) { r = mix(nr + 0.8, R * 0.76, smoothstep(3.2, 14.0, u)); part = 1.0; }\n else if (u < 16.5) { r = R * 0.94; }\n else if (u > 44.0 && u < 47.0) { part = 3.0; }\n else if (u > L - 7.0) { r = R * 0.9; part = 5.0; }\n if (u >= c0 && u <= c1) {\n r = R + 0.9; part = 4.0;\n // the rim at the cap's open end (toward the barrel while it is on, toward the nib once posted), the clip along its top\n let atBack = u > c1 - 2.2;\n let atFront = u < c0 + 2.2;\n if (select(atFront, atBack, cap < 0.5)) { part = 7.0; }\n else if (w < 1.15 && u > c0 + 6.0 && u < c1 - 7.0) { part = 6.0; }\n }\n // a rounded box in (u, w): the nearer end's rounding, never past the tube's own radius\n let start = u < (u0 + u1) * 0.5;\n let rc0 = select(nr, R + 0.9, c0 < 0.5);\n let rc1 = select(2.4, R + 0.9, c1 > L - 0.5);\n let rc = min(select(rc1, rc0, start), r);\n let de = select(u - u1, u0 - u, start);\n let dw = w - r;\n let q = vec2f(de, dw) + vec2f(rc);\n let d = length(max(q, vec2f(0.0))) + min(max(q.x, q.y), 0.0) - rc;\n let s = select(clamp((u - (u1 - rc)) / max(rc, 1.0e-3), 0.0, 1.0), -clamp(((u0 + rc) - u) / max(rc, 1.0e-3), 0.0, 1.0), start);\n return vec4f(d, part, r, s);\n}\n\n// The pen's silhouette for its SHADOW: the same tube, its steps smoothed out — a shadow is soft,\n// and a step in the radius (the nib's holder, the cap's lip) would print a line across it.\nfn bd_pen_soft(m: vec2f, cap: f32, nib: f32, k: BoardUniforms) -> f32 {\n let L = k.pen.x;\n let R = k.pen.y;\n let capL = k.pen.z;\n let c0 = mix(-2.0, L - capL * 0.55, cap);\n let c1 = c0 + capL;\n let u0 = min(0.0, c0);\n let u1 = max(L, c1);\n let uu = clamp(m.x, u0, u1);\n let capped = smoothstep(c0 - 3.0, c0 + 3.0, uu) * (1.0 - smoothstep(c1 - 3.0, c1 + 3.0, uu));\n let tube = mix(max(nib, 0.6), R, smoothstep(0.0, 16.0, uu)) - 0.1 * R * smoothstep(L - 10.0, L - 4.0, uu);\n let r = mix(tube, R + 0.9, capped);\n return length(vec2f(m.x - uu, m.y)) - r;\n}\n\n// Its colour at a point of it, seen from above: a tube lying on its side — its across-position\n// turns the normal, and on a rounded end its along-position too; the lamp shades it and the\n// gloss of moulded plastic glints on it; the part picks the material. The clip is a little\n// half-round rod on the cap, lit on the lamp's side.\nfn bd_marker_colour(u: MatUniforms, k: BoardUniforms, sd: vec4f, across: f32, axis: vec2f, perp: vec2f, ink: vec3f, L: vec3f, gobo: f32, noise: f32) -> vec3f {\n var t = clamp(across / max(sd.z, 1.0e-3), -1.0, 1.0);\n if (sd.y == 6.0) { t = clamp(across / 1.15, -1.0, 1.0) * 0.85; }\n let s = sd.w;\n var n = normalize(vec3f(perp * t + axis * s, sqrt(max(1.0 - t * t - s * s, 0.02))));\n var albedo = k.barrel.rgb;\n var gloss = 0.3;\n if (sd.y == 0.0) { albedo = ink * 0.8; gloss = 0.0; } // the felt nib\n else if (sd.y == 1.0) { albedo = k.barrel.rgb * 0.8; } // the tip's holder, a greyer plastic\n else if (sd.y == 3.0) { albedo = ink; } // the stripe that names the ink\n else if (sd.y == 4.0) { albedo = ink; gloss = 0.45; } // the cap\n else if (sd.y == 5.0) { albedo = ink * 0.9; } // the end plug\n else if (sd.y == 6.0) { albedo = mix(ink, k.barrel.rgb, 0.18); gloss = 0.7; } // the clip\n else if (sd.y == 7.0) { albedo = ink * 0.7; } // the cap's rim\n // a small round thing wants more modelling than a flat face: the lamp's fill is less on it\n let lam = 0.32 + 0.68 * min(max(dot(n, L), 0.0) / max(L.z, 0.05), k.light.y);\n albedo = albedo * pow(lam, 1.0 / 2.2);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, noise, k.light.z);\n // the gloss: the lamp's glint where it can land, and — where it cannot (a lamp along the axis\n // has no mirror line on a tube) — the bright sky over the desk, a soft stripe along the top\n let toward = L.xy / max(length(L.xy), 1.0e-4);\n let sky = pow(max(dot(n, normalize(vec3f(toward * 0.35, 1.0))), 0.0), 18.0);\n return c + vec3f((bd_glint(n, L, 40.0) + 0.3 * bd_glint(n, L, 8.0) + 0.55 * sky) * gloss * bd_lit(u, k) * mix(0.45, 1.0, gobo));\n}\n\n// ---- the melamine's sheen\n\n// The window the lamp shines through, mirrored in the melamine and seen by an eye over the\n// board: a soft disc of sky toward the lamp, high up; Schlick's Fresnel on melamine's f0. The\n// eye rides the pointer (`B.sheen`), so the reflection slides as the hand moves.\nfn bd_sheen(B: Board, k: BoardUniforms, p: vec2f, n: vec3f) -> f32 {\n let eye = vec3f(B.centre + B.sheen * B.half, k.sheen.z * 2.0 * B.half.y);\n let V = normalize(eye - vec3f(p, 0.0));\n let R = reflect(-V, n);\n let toward = B.lamp.xy / max(length(B.lamp.xy), 1.0e-4);\n let W = normalize(vec3f(toward * 0.42, 0.91));\n let w = smoothstep(cos(k.sheen.y), 1.0, dot(R, W));\n let f = 0.045 + 0.955 * pow(1.0 - max(dot(n, V), 0.0), 5.0);\n return w * w * (f / 0.045);\n}\n\n// ---- the board\n\n// One board at a world point `p`: premultiplied colour. `px` is world units per DEVICE px, `css`\n// world units per CSS px; `frag` the framebuffer pixel (the blue noise's key).\nfn shade_board(B: Board, u: MatUniforms, k: BoardUniforms, p: vec2f, px: f32, css: f32, frag: vec2f,\n gobo_tex: texture_2d<f32>, gobo_samp: sampler, noise_tex: texture_2d<f32>, noise_samp: sampler) -> vec4f {\n let q = p - B.centre;\n let L = B.lamp.xyz;\n let S = B.slope;\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n let dOut = sdf_round_box(q, B.half, B.radius);\n var acc = vec4f(0.0);\n // what a shadow is made of: the world's darkest value by day (the design's cast), the dark's own grey by night\n let shadowInk = mix(k.castTint.rgb, bd_encode(u.eigengrau.xyz), clamp(u.night.x, 0.0, 1.0));\n\n // The slab's shadow on the mat, outside its footprint.\n if (dOut > -px) {\n let sh = bd_slab(q, B.half, B.radius, B.lift, B.lift + B.thick, S, k, 1.0, 6) * (1.0 - bd_cov(dOut, px));\n acc = vec4f(shadowInk * sh, sh);\n }\n\n let top = B.lift + B.thick; // the frame's top off the mat\n let face = top - B.recess; // the melamine's\n if (dOut < px) {\n let cov = bd_cov(dOut, px);\n let dIn = sdf_round_box(q, B.inner, B.innerR);\n var col: vec3f;\n if (dIn > 0.0) {\n // THE FRAME — a rounded bar: across the band the rims roll away, outward at the edge,\n // inward at the lip; brushed along its length; glinting where it turns to the lamp.\n let t = clamp(dIn / max(dIn - dOut, 1.0e-4), 0.0, 1.0);\n let e = 2.0 * t - 1.0;\n // a half-round bar: the normal turns steadily across it, steepest at the rims\n let theta = k.lip.w * sign(e) * pow(abs(e), 0.85);\n let g = bd_grad(q, B.half, B.radius);\n let n = normalize(vec3f(g * sin(theta), cos(theta)));\n let along = dot(q, vec2f(-g.y, g.x));\n let across = dot(q, g);\n let brushed = (felt_noise(vec2f(along * 0.045, across * 2.2)) + 0.5 * felt_noise(vec2f(along * 0.13 + 7.0, across * 6.1))) * k.lip.z;\n var albedo = B.metal.rgb + vec3f(brushed);\n albedo = albedo * pow(bd_lambert(n, L, k), 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, top), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n // the metal: a tight glint where the bar turns the lamp to the eye, and a broad sheen round it\n let lit = bd_lit(u, k) * mix(0.2, 1.0, gobo);\n col += vec3f((bd_glint(n, L, 90.0) + 0.6 * bd_glint(n, L, 14.0)) * k.glint.x * lit);\n // metal mirrors the sky: a face turned up and out catches it — the bar's shaded half is never dead\n col += vec3f(0.06 * smoothstep(0.2, 1.0, n.z) * bd_lit(u, k));\n } else {\n // THE MELAMINE — the raster over it: uv across the face, the ink's mip by the screen's density.\n let uv = (q + B.inner) / (2.0 * B.inner);\n let lod = max(log2(max(B.density * px / max(B.scale, 1.0e-3), 1.0e-6)), 0.0);\n var ink = board_ink(B, uv, lod); // (the entry's: where this board's ink is bound — K6a)\n if (B.laying > 0.5) {\n // the stroke being laid, shown as it will land\n let sc = board_stroke(B, uv);\n if (B.tool.w < 0.5) { ink = vec4f(ink.rgb * (1.0 - sc) + B.tool.rgb * sc, ink.a * (1.0 - sc) + sc); }\n else { ink = ink * (1.0 - sc); }\n }\n let cov2 = ink.a;\n var wet = 0.0;\n if (B.wet > 0.5) { wet = board_wet(B, uv) * smoothstep(0.0, 0.3, cov2); }\n\n // the face's normal: a slow waviness, and the ink film standing a few microns proud of it\n let dt = exp2(lod) / B.texels;\n let ax = board_ink(B, uv + vec2f(dt.x, 0.0), lod).a - board_ink(B, uv - vec2f(dt.x, 0.0), lod).a;\n let ay = board_ink(B, uv + vec2f(0.0, dt.y), lod).a - board_ink(B, uv - vec2f(0.0, dt.y), lod).a;\n let w1 = felt_noised(q * (1.0 / 80.0) + vec2f(3.1, 7.7)).yz;\n let w2 = felt_noised(q * (1.0 / 31.0) + vec2f(11.0, 5.0)).yz;\n let slope = (w1 + 0.5 * w2) * 0.006 + vec2f(ax, ay) * k.mel.w;\n let n = normalize(vec3f(-slope, 1.0));\n\n // the albedo: the melamine's tone and grain, the ghost of old erasures, the ink as a filter (linear) over it\n let tone = felt_noise(q * (1.0 / 140.0) + vec2f(20.0, 4.0)) * k.mel.x;\n let grain = (bn.x - 0.5) * k.mel.y;\n let ghost = smoothstep(0.1, 0.45, felt_fbm(q * (1.0 / 21.0) + vec2f(40.0, 3.0))) * k.mel.z;\n let base = (B.surface.rgb * (1.0 + tone) + vec3f(grain)) * (1.0 - ghost);\n let filt = vec3f(1.0 - cov2) + ink.rgb * mix(1.0, k.sheen.w, wet);\n var albedo = bd_encode(srgb_to_linear(clamp(base, vec3f(0.0), vec3f(1.0))) * filt);\n\n // the light on it: the lamp over the ink's relief, the frame's lip on the lamp's side, the corner's occlusion\n let dl = sdf_round_box(q - S * B.recess, B.inner, B.innerR);\n let lip = bd_blur(-dl, k.lip.y) * k.lip.x;\n let ao = 1.0 - 0.07 * (1.0 - smoothstep(0.0, 3.0, -dIn));\n albedo = albedo * pow(bd_lambert(n, L, k) * (1.0 - lip) * ao, 1.0 / 2.2);\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face), bn.z);\n col = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n\n // the window in the glossy face — stronger on wet ink; it shows most where the face is darkest\n // (in the leaves' shade, on the ink), as a reflection does\n let sheen = bd_sheen(B, k, p, n) * k.sheen.x * (1.0 + 1.5 * wet) * bd_lit(u, k);\n col = col + vec3f(sheen) * (vec3f(1.0) - col * 0.55);\n }\n acc = bd_over(vec4f(col * cov, cov), acc);\n }\n\n // THE PEN — one marker in whatever pose the host gives it: lying capped on the melamine, in the\n // hand at the pointer with its cap posted, or on its way between (BOARD.md §5). Its shadow first\n // — the axis point at u stands h0 + u·rise over the melamine and casts along the lamp's slope,\n // softened by that height; a lying pen has its contact line — then the pen itself.\n if (B.pen.w > 0.001) {\n let a = vec2f(cos(B.pen.z), sin(B.pen.z));\n let perp = vec2f(-a.y, a.x);\n let tip = B.pen.xy;\n let h0 = B.penPose.x;\n let rise = B.penPose.y;\n let cap = B.penPose.z;\n let nib = B.penPose.w;\n let pres = B.pen.w;\n if (distance(p, tip) < k.pen.x * 1.3 + 90.0) {\n let D = a + S * rise;\n let A0 = tip + S * h0;\n let c0 = mix(-2.0, k.pen.x - k.pen.z * 0.55, cap);\n let tt = clamp(dot(p - A0, D) / max(dot(D, D), 1.0e-6), min(0.0, c0), max(k.pen.x, c0 + k.pen.z));\n let hs = h0 + tt * rise;\n let rs = p - (A0 + D * tt);\n let msd = bd_pen_soft(vec2f(tt, length(rs)), cap, nib, k);\n let shade = bd_blur(msd, k.shadow.x + k.shadow.y * HAND_PENUMBRA * hs) * k.shadow.z * HAND_UMBRA;\n let rc = p - tip;\n let mc = vec2f(dot(rc, a), dot(rc, perp));\n // lying, it touches the melamine along a line: the contact shadow, gone as it lifts\n let lying = exp(-max(h0 - k.pen.y, 0.0) / 2.5) * (1.0 - smoothstep(0.0, 0.05, rise));\n let contact = bd_blur(bd_pen_soft(mc, cap, nib, k) + k.pen.y * 0.45, k.shadow2.x * 1.4) * 0.42 * lying;\n let msh = min(max(shade, contact), 1.0) * pres;\n acc = bd_over(vec4f(shadowInk * msh, msh), acc);\n let sd = bd_pen(mc, cap, nib, k);\n let mcov = bd_cov(sd.x, px) * pres;\n if (mcov > 0.0) {\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + h0 + max(mc.x, 0.0) * rise + k.pen.y), bn.z);\n let c = bd_marker_colour(u, k, sd, mc.y, a, perp, B.penInk.rgb, L, gobo, bn.y);\n acc = bd_over(vec4f(c * mcov, mcov), acc);\n }\n }\n }\n\n // THE ERASER in the hand: a felt block with a wooden back, square to its angle.\n if (B.eraser.w > 0.001 && distance(p, B.eraser.xy) < 120.0) {\n let ang = k.block2.x;\n let a = vec2f(cos(ang), sin(ang));\n let perp = vec2f(-a.y, a.x);\n let rc = p - B.eraser.xy;\n let qe = vec2f(dot(rc, a), dot(rc, perp));\n let half = k.block.xy;\n let gap = B.eraser.z;\n let hTop = gap + k.block.z + k.block.w;\n let pres = B.eraser.w;\n let sh = min(bd_slab(qe, half, 3.0, gap, hTop, vec2f(dot(S, a), dot(S, perp)), k, HAND_PENUMBRA, 14) * HAND_UMBRA, 1.0) * pres; // the slope, in the block's frame\n acc = bd_over(vec4f(shadowInk * sh, sh), acc);\n let dFelt = sdf_round_box(qe, half, 3.0);\n let fcov = bd_cov(dFelt, px) * pres;\n if (fcov > 0.0) {\n let dWood = sdf_round_box(qe, half - vec2f(1.5), 2.4);\n let gw = bd_grad(qe, half - vec2f(1.5), 2.4);\n let bevel = 1.0 - smoothstep(0.0, 2.2, -dWood);\n let gw3 = vec2f(gw.x * a.x - gw.y * a.y, gw.x * a.y + gw.y * a.x); // back to world axes\n let n = normalize(vec3f(gw3 * sin(bevel * 0.9), cos(bevel * 0.9)));\n let grain = felt_noise(vec2f(qe.x * 0.06, qe.y * 1.3)) * 0.05 + felt_noise(vec2f(qe.x * 0.21, qe.y * 3.7)) * 0.025;\n var albedo = k.felt.rgb;\n if (dWood < 0.0) {\n // the wooden back, its grain along the block, a finger groove routed round its top\n let groove = 1.0 - smoothstep(0.25, 0.9, abs(sdf_round_box(qe, half - vec2f(6.5, 3.4), 1.8)));\n albedo = (k.wood.rgb + vec3f(grain)) * pow(bd_lambert(n, L, k), 1.0 / 2.2) * (1.0 - 0.2 * groove);\n }\n let gobo = sample_gobo(u, gobo_tex, gobo_samp, bd_desk_at(u, p, face + hTop), bn.z);\n let c = bd_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.light.z);\n acc = bd_over(vec4f(c * fcov, fcov), acc);\n }\n }\n\n // The sole-selection ring, inside the outer edge, in the theme's select.\n let ring = bd_line(dOut, k.shadow2.z * css, px) * B.ring;\n acc = bd_over(vec4f(k.select.rgb * ring, ring), acc);\n return acc * B.alpha;\n}\n";
5
+ readonly "board/felt.wgsl": "// The FELT's noise (BOARD.md) — a PURE module: the research whiteboard's gradient noise\n// (research/whiteboard noise.wgsl) with its analytic derivatives, an integer hash so it is\n// the same on every GPU, prefixed `felt_` beside the mat's own `hash12` / `value_noise`. The\n// pen's fibre lanes read it in the stamp; the melamine's waviness and orange peel, the\n// frame's brushing and the eraser's wood read it on the desk.\n\nfn felt_hash(p: vec2u) -> u32 {\n var v = p.x * 1664525u + 1013904223u;\n v = v ^ (p.y * 22695477u + 374761393u);\n v = v ^ (v >> 16u);\n v = v * 0x7feb352du;\n v = v ^ (v >> 15u);\n v = v * 0x846ca68bu;\n v = v ^ (v >> 16u);\n return v;\n}\n\nfn felt_grad(i: vec2i) -> vec2f {\n let h = felt_hash(bitcast<vec2u>(i));\n let a = f32(h & 0xffffu) * (6.283185307 / 65536.0);\n return vec2f(cos(a), sin(a));\n}\n\n// Gradient noise with its derivatives: (value, d/dx, d/dy); the value roughly in [−0.7, 0.7].\nfn felt_noised(p: vec2f) -> vec3f {\n let i = vec2i(floor(p));\n let f = fract(p);\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 ga = felt_grad(i);\n let gb = felt_grad(i + vec2i(1, 0));\n let gc = felt_grad(i + vec2i(0, 1));\n let gd = felt_grad(i + vec2i(1, 1));\n let va = dot(ga, f);\n let vb = dot(gb, f - vec2f(1.0, 0.0));\n let vc = dot(gc, f - vec2f(0.0, 1.0));\n let vd = dot(gd, f - vec2f(1.0, 1.0));\n let k = va - vb - vc + vd;\n let v = va + u.x * (vb - va) + u.y * (vc - va) + u.x * u.y * k;\n let d = ga + u.x * (gb - ga) + u.y * (gc - ga) + u.x * u.y * (ga - gb - gc + gd)\n + du * (u.yx * k + vec2f(vb, vc) - va);\n return vec3f(v, d.x, d.y);\n}\n\nfn felt_noise(p: vec2f) -> f32 { return felt_noised(p).x; }\n\nfn felt_fbm(p: vec2f) -> f32 {\n var v = 0.0;\n var a = 0.5;\n var q = p;\n for (var i = 0; i < 4; i++) {\n v = v + a * felt_noise(q);\n q = q * 2.03 + vec2f(1.7, 9.2);\n a = a * 0.5;\n }\n return v;\n}\n";
6
+ readonly "board/ink.wgsl": "// The INK — fullscreen passes over a board's raster (the research whiteboard's\n// composite.wgsl): lay a finished stroke from the stroke layer into the ink layer, mark it wet,\n// let the wet fade. The ink layer is PREMULTIPLIED LINEAR colour with coverage in alpha, so\n// its mips average correctly and the melamine can read it as a filter.\n\n@group(0) @binding(0) var<uniform> C: InkUniforms;\n@group(0) @binding(1) var stroke: texture_2d<f32>;\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {\n let x = f32((vi << 1u) & 2u);\n let y = f32(vi & 2u);\n return vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n}\n\nfn ink_cov(p: vec4f) -> f32 { return textureLoad(stroke, vec2i(p.xy), 0).r; }\n\n// a marker: premultiplied \"over\" into the ink layer (blend one / one-minus-src-alpha)\n@fragment\nfn fs_draw(@builtin(position) p: vec4f) -> @location(0) vec4f {\n let c = ink_cov(p);\n return vec4f(C.color.rgb * c, c);\n}\n\n// the eraser: ink *= 1 − coverage (blend zero / one-minus-src-alpha)\n@fragment\nfn fs_erase(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(0.0, 0.0, 0.0, ink_cov(p));\n}\n\n// the wet layer: max(wet, coverage)\n@fragment\nfn fs_wet(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(ink_cov(p), 0.0, 0.0, 1.0);\n}\n\n// the wet fades: dst *= the blend constant\n@fragment\nfn fs_dry(@builtin(position) p: vec4f) -> @location(0) vec4f {\n return vec4f(0.0);\n}\n";
7
+ readonly "board/mip.wgsl": "// The MIP — one level of a board's ink raster from the level above: each texel the bilinear\n// average of the 2×2 it covers (premultiplied, so the average is the right one). A board seen\n// small reads its ink here instead of aliasing the full raster.\n\n@group(0) @binding(0) var src: texture_2d<f32>;\n@group(0) @binding(1) var src_samp: sampler;\n\nstruct MipOut {\n @builtin(position) pos: vec4f,\n @location(0) uv: vec2f,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32) -> MipOut {\n let x = f32((vi << 1u) & 2u);\n let y = f32(vi & 2u);\n var o: MipOut;\n o.pos = vec4f(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);\n o.uv = vec2f(x, y);\n return o;\n}\n\n@fragment\nfn fs(in: MipOut) -> @location(0) vec4f {\n return textureSampleLevel(src, src_samp, in.uv, 0.0);\n}\n";
8
+ readonly "board/stamp.wgsl": "// The STAMP — the pen's footprints into the stroke layer (r8, max-blended: a stroke never\n// darkens where it crosses itself, as a marker line does not). One instance per stamp, its\n// quad grown from the stamp's centre by the tip's reach, in the raster's texels; the\n// footprint, the felt's lanes and the flow in WORLD units (the stamps are the melamine's world\n// units, stroke.ts), so a stroke is the same stroke at any density. The research\n// whiteboard's stamp.wgsl, moved off the screen and onto the board.\n\n@group(0) @binding(0) var<uniform> S: StampUniforms;\n@group(0) @binding(1) var<storage, read> stamps: array<Stamp>;\n\nstruct StampOut {\n @builtin(position) pos: vec4f,\n @location(0) px: vec2f,\n @location(1) @interpolate(flat) inst: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> StampOut {\n let st = stamps[ii];\n let k = S.tex.z; // texels per world unit\n let corner = vec2f(f32(vi & 1u), f32((vi >> 1u) & 1u)) * 2.0 - 1.0;\n let ext = (length(S.tip.xy) * st.size + S.felt.x + 0.5) * k + 2.0;\n let px = st.pos * k + corner * ext;\n var o: StampOut;\n o.pos = vec4f(px / S.tex.xy * vec2f(2.0, -2.0) + vec2f(-1.0, 1.0), 0.0, 1.0);\n o.px = px;\n o.inst = ii;\n return o;\n}\n\n@fragment\nfn fs(in: StampOut) -> @location(0) vec4f {\n let st = stamps[in.inst];\n let k = S.tex.z;\n let texel = 1.0 / k;\n let d = (in.px - st.pos * k) * texel; // world units from the stamp's centre\n\n // the footprint in the tip's own frame (the tip keeps its angle, like a hand-held marker)\n let ca = cos(S.tip.z);\n let sa = sin(S.tip.z);\n let q = vec2f(ca * d.x + sa * d.y, -sa * d.x + ca * d.y);\n let hf = S.tip.xy * st.size;\n let sd = sdf_round_box(q, hf, S.tip.w * st.size);\n let soft = S.felt.x;\n let shape = 1.0 - smoothstep(-soft * 0.5 - texel * 0.5, soft * 0.5 + texel * 0.5, sd);\n\n // the felt's fibres: each traces a line parallel to the motion, so the lane pattern is a\n // function of the offset across the path (stable under max-blending of the stamps)\n let perp = vec2f(-st.dir.y, st.dir.x);\n let across = dot(d, perp);\n let along = st.s;\n let n1 = felt_noise(vec2f(across * S.lanes.x + st.seed * 37.1, along * S.lanes.y + st.seed * 11.3));\n let n2 = felt_noise(vec2f(across * S.lanes.x * 1.7 + st.seed * 5.7, along * S.lanes.z + st.seed * 3.1));\n let lanes = smoothstep(0.08, 0.5, n1 + 0.45 * n2);\n\n var density: f32;\n if (S.felt.y < 0.5) {\n // a marker: mostly solid ink; a faster stroke runs drier and opens more, deeper lanes\n let dry = 1.0 - st.flow;\n let streak = clamp(S.felt.w * (1.0 + 5.0 * dry), 0.0, 1.0);\n let flowVar = 1.0 + 0.04 * felt_noise(vec2f(along * S.lanes.y, st.seed * 3.3 + 50.0));\n // the felt presses lighter at its rim\n let rim = 1.0 - 0.22 * smoothstep(-0.35 * min(hf.x, hf.y), 0.0, sd);\n density = (1.0 - streak * lanes) * (1.0 - 0.35 * dry) * flowVar * rim * S.felt.z;\n } else {\n // the eraser: takes most of the ink, leaves a streaky ghost\n density = 0.86 + 0.11 * (1.0 - lanes);\n }\n return vec4f(clamp(shape * density, 0.0, 1.0), 0.0, 0.0, 1.0);\n}\n";
9
+ readonly "calendar/calendar-pass.wgsl": "// The calendar pass's entries (CALENDAR.md §6). A pad's record is always read through a POINTER into the storage\n// buffer (`&pads[i]`): a by-value copy of it (a kilobyte, with arrays indexed at run time) is pushed out of\n// registers into memory and cost a full-screen pad ~10 ms. Into a 4× multisampled layer, per calendar:\n// vs_moving · fs_sheet the sheet in motion (first, so the month it covers is skipped by the depth test): a grid\n// laid on the sheet, its rows fine only where the roll is, bent by the roll (cal_roll)\n// vs_pad · fs_face the month on the pad: its face — the largest thing on the screen, a lean shader of its own\n// vs_pad · fs_sheet the roll of the months before (seen from behind as much as not)\n// vs_pad · fs_solid the pad's solid parts: its sides, the chipboard, the cloth tape (a lean shader of their own)\n// vs_recv · fs_recv the mat under it: its shadow, its contact, its selection ring — after the pad, so\n// the depth test skips every pixel the pad covers\n// The layer is resolved and laid on the mat INSIDE the ground's pass, before the cards and the notes\n// (ground.ts `underlays`): the pad lies beneath everything else on the desk.\n// The view block `u` is the SLOT's since K4a (design-016 K-L3), shared with the mat and every kind: this layer is drawn at FULL\n// presence, as it always was (the pass's own copy of the block carried no presentation, and the composite lays the layer whole),\n// so it reads neither `u.view.w` (the objects' presence) nor `u.presence.x` (the slot's) — its alpha is its coverage alone.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> k: CalUniforms;\n@group(0) @binding(2) var<storage, read> pads: array<CalPad>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>;\n@group(0) @binding(4) var gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var noise_samp: sampler;\n@group(0) @binding(7) var tile_tex: texture_2d_array<f32>; // the print's tiles (straight alpha), 260² each\n@group(0) @binding(8) var tile_samp: sampler;\n@group(0) @binding(9) var<storage, read> tiles: array<i32>; // the page tables: a layer, −1 missing, −2 empty\n@group(0) @binding(10) var paper_tex: texture_2d<f32>;\n@group(0) @binding(11) var paper_samp: sampler;\n\n// The print at a sheet point, from the table `slot`: the level at or above the screen's density (`bias`\n// rungs coarser), falling back a rung at a time to one that is there. Transparent where nothing is printed.\nfn cal_tile(slot: f32, q: vec2f, px: f32, bias: i32) -> vec4f {\n if (slot < 0.0) { return vec4f(0.0); }\n let base = i32(slot + 0.5) * i32(k.tiles.x + 0.5);\n var l = clamp(i32(ceil(2.0 * log2(max(1.0 / px, 1e-6) / 0.25) - 1e-3)) - bias, 0, 10);\n for (var i = 0; i < 11; i++) {\n let band = 0.25 * exp2(f32(l) * 0.5);\n let t = q * (band / k.tiles.y);\n let ti = vec2i(floor(t));\n let nx = i32(k.tileNx[l / 4][l % 4] + 0.5);\n let ny = i32(k.tileNy[l / 4][l % 4] + 0.5);\n if (ti.x < 0 || ti.y < 0 || ti.x >= nx || ti.y >= ny) { return vec4f(0.0); }\n let e = tiles[base + i32(k.tileOff[l / 4][l % 4] + 0.5) + ti.y * nx + ti.x];\n if (e >= 0) {\n let uv = (fract(t) * k.tiles.y + vec2f(k.tiles.z)) / k.tiles.w;\n return textureSampleLevel(tile_tex, tile_samp, uv, e, 0.0);\n }\n if (e == -2 || l == 0) { return vec4f(0.0); }\n l -= 1;\n }\n return vec4f(0.0);\n}\n\nstruct VIn {\n @location(0) pos: vec3f,\n @location(1) nrm: vec3f,\n @location(2) uv: vec2f,\n @location(3) mat: vec4f, // material, unused, flags, ao\n @location(4) size: vec2f,\n @location(5) extra: vec2f,\n}\n\nstruct VOut {\n @builtin(position) clip: vec4f,\n @location(0) world: vec3f,\n @location(1) nrm: vec3f,\n @location(2) uv: vec2f, // the SHEET point (world units from the sheet's top-left)\n @location(3) @interpolate(flat) mat: vec3f, // material, flags, the calendar\n @location(4) ao: f32,\n @location(5) local: vec3f, // the pad's own coordinates\n}\n\n@vertex\nfn vs_pad(v: VIn, @builtin(instance_index) iid: u32) -> VOut {\n let B = &pads[iid];\n var out: VOut;\n var p = v.pos;\n // the past roll lies a hair over the sheet coming off it, so the two never fight\n if (i32(v.mat.x + 0.5) == 2) { p.z += 0.12; }\n let P = ((*B).model * vec4f(p, 1.0)).xyz;\n out.clip = cal_clip(k, P);\n out.world = P;\n out.nrm = ((*B).model * vec4f(v.nrm, 0.0)).xyz;\n out.uv = v.uv;\n out.mat = vec3f(v.mat.x, v.mat.z, f32(iid));\n out.ao = v.mat.w;\n out.local = p;\n return out;\n}\n\n@vertex\nfn vs_moving(@location(0) g: vec2f, @builtin(instance_index) iid: u32) -> VOut {\n let B = &pads[iid];\n var out: VOut;\n let W = (*B).size.x;\n let H = (*B).size.y;\n let T = (*B).size.z;\n let L = (*B).size.w;\n let sx = g.x * W;\n // the grid's rows laid where they are needed: a dozen over the flat part, the rest from just before the\n // tangent line (wherever it crosses the sheet) to just past the turns drawn — beyond that is inside the roll\n let ta = tan((*B).roll.y);\n let a0 = (*B).roll.x + (0.0 - (*B).roll.z) * ta;\n let a1 = (*B).roll.x + (W - (*B).roll.z) * ta;\n let sA = clamp(min(a0, a1) - 24.0, 0.0, L);\n let turnLen = ((*B).roll2.w + 2.0) * (*B).roll2.y;\n let sB = clamp(max(a0, a1) + turnLen / max(cos((*B).roll.y), 0.3) + 24.0, sA, L);\n let v0 = 12.0 / 220.0;\n var s = sA * g.y / v0;\n if (g.y > v0) { s = sA + (sB - sA) * (g.y - v0) / (1.0 - v0); }\n var r = cal_roll((*B).roll.x, (*B).roll.y, (*B).roll.z, (*B).roll.w, (*B).roll2.x, L, sx, s);\n // past the turns drawn a point is inside the roll: it folds onto the last one drawn (its triangles vanish)\n if (r.phi > (*B).roll2.y) {\n let sa = sin((*B).roll.y);\n let ca = cos((*B).roll.y);\n let d = (sx - (*B).roll.z) * (-sa) + (s - (*B).roll.x) * ca;\n let D = max((sx - (*B).roll.z) * (-sa) + (L - (*B).roll.x) * ca, d);\n let R = cal_radius((*B).roll.w, (*B).roll2.x, D);\n let pm = (*B).roll2.y;\n let dc = R * pm - (*B).roll2.x * pm * pm / (4.0 * CAL_PI);\n r = cal_roll((*B).roll.x, (*B).roll.y, (*B).roll.z, (*B).roll.w, (*B).roll2.x, L, sx + (d - dc) * sa, s - (d - dc) * ca);\n }\n let local = vec3f(r.p.x - W * 0.5, -H * 0.5 + T + r.p.y, (*B).z.z + r.p.z);\n let P = ((*B).model * vec4f(local, 1.0)).xyz;\n out.clip = cal_clip(k, P);\n out.world = P;\n out.nrm = ((*B).model * vec4f(r.n, 0.0)).xyz;\n out.uv = vec2f(sx, T + s);\n out.mat = vec3f(1.0, 1.0, f32(iid));\n out.ao = 1.0;\n out.local = local;\n return out;\n}\n\n// The rolls' and the tape's shade on a flat sheet at pad point `p` (height `zr`): the lamp's light that gets\n// through (vis), and the creases' occlusion (ao). Each term only where it can reach: the past roll's within a\n// shadow's length of the head, the moving roll's near its line, the tape's lip right under it.\nfn cal_sheet_shade(pi: u32, p: vec2f, zr: f32, L: vec3f, moving: bool) -> vec2f {\n let B = &pads[pi]; // a pointer: a copy of the record would be pushed out of registers\n let W = (*B).size.x;\n let H = (*B).size.y;\n let T = (*B).size.z;\n let reach = 2.2 * 60.0 + 40.0; // the longest shadow a roll lying on the pad throws, and its penumbra\n var vis = 1.0;\n var ao = 1.0;\n // the roll of the months before, against the tape\n let R0 = (*B).roll2.w;\n let o0 = vec2f(-W * 0.5, -H * 0.5 + T + R0);\n if (zr < (*B).z.z + R0 && abs(p.y - o0.y) < reach) {\n vis *= 1.0 - 0.72 * (1.0 - cal_cyl_shadow(k, p, zr, o0, vec2f(1.0, 0.0), R0, (*B).z.z + 0.12 + R0, L, 0.0, W));\n ao *= cal_crease(p, o0, vec2f(1.0, 0.0), R0);\n }\n // the moving sheet's roll, where its tangent line is\n if ((*B).roll2.z > 0.5) {\n let a = (*B).roll.x;\n let al = (*B).roll.y;\n let dir = vec2f(cos(al), sin(al));\n let o = vec2f((*B).roll.z - W * 0.5, -H * 0.5 + T + a);\n let dn = dot(p - o, vec2f(-dir.y, dir.x));\n if (abs(dn) < reach) {\n let sx = p.x + W * 0.5;\n let D = max((sx - (*B).roll.z) * (-sin(al)) + ((*B).size.w - a) * cos(al), 0.0);\n let R = cal_radius((*B).roll.w, (*B).roll2.x, D) * smoothstep(0.0, 6.0, D);\n let t0 = -dot(o - vec2f(-W * 0.5, 0.0), dir);\n if (R > 0.0 && zr < (*B).z.z + R) {\n vis *= 1.0 - 0.72 * (1.0 - cal_cyl_shadow(k, p, zr, o, dir, R, (*B).z.z + R, L, t0, t0 + W / max(dir.x, 0.2)));\n if (!moving) { ao *= cal_crease(p, o, dir, R); }\n }\n }\n }\n // the tape's lip over the sheets: a hairline of contact below its edge\n let below = p.y - (-H * 0.5 + T);\n if (below >= 0.0 && below < 12.0) { ao *= 1.0 - 0.28 * exp(-below / 1.6); }\n return vec2f(vis, ao);\n}\n\n// The face's tangents (world units along the sheet's x and y) from the uv's screen derivatives.\nfn cal_tangents(n: vec3f, dPdx: vec3f, dPdy: vec3f, dqdx: vec2f, dqdy: vec2f) -> mat2x3f {\n let det = dqdx.x * dqdy.y - dqdx.y * dqdy.x;\n var Tu = vec3f(1.0, 0.0, 0.0);\n var Tv = vec3f(0.0, 1.0, 0.0);\n if (abs(det) > 1e-14) {\n Tu = (dPdx * dqdy.y - dPdy * dqdx.y) / det;\n Tv = (dPdy * dqdx.x - dPdx * dqdy.x) / det;\n }\n Tu = Tu - n * dot(n, Tu);\n Tv = Tv - n * dot(n, Tv);\n Tu = select(vec3f(1.0, 0.0, 0.0), normalize(Tu), length(Tu) > 1e-8);\n Tv = select(vec3f(0.0, 1.0, 0.0), normalize(Tv), length(Tv) > 1e-8);\n return mat2x3f(Tu, Tv);\n}\n\n// The lamp on a face: the bumped normal, the sky's share, the leaves' dapple, the desk's day or night, the sheen.\nfn cal_lit(albedo: vec3f, n: vec3f, T: mat2x3f, grad: vec2f, world: vec3f, V: vec3f, Lw: vec3f, vis: f32, ao: f32, paper: bool, back: bool, dapple: f32, sheen: vec2f, bn: vec3f) -> vec3f {\n let nb = normalize(n - T[0] * grad.x - T[1] * grad.y);\n let lam = min(max(dot(nb, Lw), 0.0) / max(Lw.z, 1e-3), k.light.y);\n let sky = select(k.light.x, mix(k.light.x, 1.0, 0.22), paper);\n var light = (sky + (1.0 - sky) * lam * vis) * ao;\n if (paper && back) {\n // paper is translucent: the back of a curling sheet glows with the lamp behind it\n light += 0.18 * (1.0 - k.light.x) * max(-dot(n, Lw), 0.0) * ao;\n }\n var g0 = 1.0;\n if ((u32(k.ring.y) & 4u) == 0u) { g0 = sample_gobo(u, gobo_tex, gobo_samp, nb_desk_at(u, world), bn.z); }\n let gobo = mix(1.0, g0, dapple);\n var c = nb_colour(u, albedo, light, gobo, bn.y);\n let Hh = normalize(Lw + V);\n let sp = pow(max(dot(nb, Hh), 0.0), sheen.x) * sheen.y * vis * g0;\n return c + vec3f(sp * (1.0 - u.night.x * 0.85));\n}\n\n// The print on a sheet's FACE: the grid it is printed on, the tiles' ink, the pen's caret and wipe (onto the\n// albedo), and the host's marks (returned: their coverage). `moving` picks the sheet's grid and table.\nstruct CalFace { albedo: vec3f, marks: f32 }\nfn cal_face(pi: u32, albedo0: vec3f, q: vec2f, px: f32, moving: bool, dbg: u32) -> CalFace {\n let B = &pads[pi];\n var out: CalFace;\n var albedo = albedo0;\n let G0 = select((*B).gridA, (*B).gridC, moving);\n let G1 = select((*B).gridB, (*B).gridD, moving);\n let g = cal_grid(k, q, px, G0, G1);\n albedo *= mix(vec3f(1.0), (*B).col3.rgb, g.weekend * k.alpha.z + g.outside * k.alpha.w);\n albedo *= mix(vec3f(1.0), (*B).col2.rgb, g.rule * k.alpha.x);\n albedo *= mix(vec3f(1.0), (*B).col1.rgb, g.head * k.alpha.y);\n var ink = vec4f(0.0);\n if ((dbg & 64u) == 0u) { ink = cal_tile(select((*B).slots.x, (*B).slots.y, moving), q, px, 0); }\n let onMarks = ((*B).slots.z > 0.5) == moving;\n out.marks = 0.0;\n if (onMarks) {\n // the newest glyph is still being written: the pen's stroke reveals it left to right\n let wp = (*B).wipe;\n if ((*B).wipe2.x < 1.0 && q.x >= wp.x && q.x <= wp.z && q.y >= wp.y && q.y <= wp.w) {\n ink.a *= 1.0 - smoothstep(mix(wp.x, wp.z, (*B).wipe2.x) - 1.5, mix(wp.x, wp.z, (*B).wipe2.x) + 1.5, q.x);\n }\n }\n albedo *= mix(vec3f(1.0), ink.rgb, ink.a);\n if (onMarks) {\n let cr = (*B).caret;\n if (cr.w > 0.0) {\n albedo = mix(albedo, (*B).col8.rgb, nb_cov(abs(q.x - cr.x) - cr.w * 0.5, px) * nb_cov(max(cr.y - q.y, q.y - cr.z), px) * 0.92);\n }\n if ((dbg & 32u) == 0u) {\n // the marks the host draws: the days selected (as many boxes as `wipe2.w` says), the entry selected, the day a note would stick to\n let w = k.select.w / max(k.eye.w, 1e-6);\n let reach = 16.0 / max(k.eye.w, 1e-6); // the marks' brackets reach 16 CSS px (D3t-c)\n let nSel = i32((*B).wipe2.w + 0.5);\n for (var i = 0; i < nSel; i++) { out.marks = max(out.marks, cal_brackets(q, (*B).sel[i], w, 3.0, px, reach)); }\n out.marks = max(out.marks, cal_brackets(q, (*B).mark, w, 4.0, px, reach));\n out.marks = max(out.marks, cal_outline(q, (*B).drop, w * 1.4, 4.0, px) * 0.85);\n }\n }\n out.albedo = albedo;\n return out;\n}\n\n// The month on the pad (mat 0): the pad's face — the largest thing on the screen, so its own lean shader: a\n// face only, never seen from behind, never rolled.\n@fragment\nfn fs_face(in: VOut) -> @location(0) vec4f {\n let dPdx = dpdx(in.world);\n let dPdy = dpdy(in.world);\n let dqdx = dpdx(in.uv);\n let dqdy = dpdy(in.uv);\n let pi = u32(in.mat.z + 0.5);\n let B = &pads[pi];\n let px = max(max(length(dqdx), length(dqdy)), 1e-4);\n let n = vec3f(0.0, 0.0, 1.0);\n let E = vec3f(k.eye.x, k.eye.y, k.eye.z);\n let V = normalize(E - in.world);\n let q = in.uv;\n let dbg = u32(k.ring.y);\n if ((dbg & 512u) != 0u) { return vec4f((*B).col0.rgb, 1.0); }\n let TT = mat2x3f(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 1.0, 0.0)); // a pad lies square: its x and y are the world's\n let bn = textureSampleLevel(noise_tex, noise_samp, (in.clip.xy + k.shadow2.yz) * u.noise.z + u.noise.xy, 0.0).rgb;\n let Lw = (*B).lamp.xyz;\n let pp = cal_paper(k, (*B).col0.rgb, q, dqdx, dqdy, 3.0, paper_tex, paper_samp);\n var face = CalFace(pp.albedo, 0.0);\n if ((dbg & 2u) == 0u) { face = cal_face(pi, pp.albedo, q, px, false, dbg); }\n var sh = vec2f(1.0);\n if ((dbg & 8u) == 0u) { sh = cal_sheet_shade(pi, in.local.xy, in.local.z, Lw, false); }\n var c = cal_lit(face.albedo, n, TT, pp.g, in.world, V, Lw, sh.x, in.ao * sh.y, true, false, k.light.z, vec2f(5.0, 0.02), bn);\n c = mix(c, k.select.rgb, face.marks);\n return vec4f(c, 1.0);\n}\n\n// A SHEET that may be seen from behind: the one in motion (mat 1), the roll of the months before (2) — the ivory,\n// its fibre and tooth; on a face, the print; on a back, the month showing through; the rolls' shade on the flat.\n@fragment\nfn fs_sheet(in: VOut) -> @location(0) vec4f {\n // every derivative first — they must be taken in uniform control flow\n let dPdx = dpdx(in.world);\n let dPdy = dpdy(in.world);\n let dqdx = dpdx(in.uv);\n let dqdy = dpdy(in.uv);\n let pi = u32(in.mat.z + 0.5);\n let B = &pads[pi];\n let px = max(max(length(dqdx), length(dqdy)), 1e-4); // sheet units per device px\n let mat = i32(in.mat.x + 0.5);\n var n = normalize(in.nrm);\n let E = vec3f(k.eye.x, k.eye.y, k.eye.z);\n let V = normalize(E - in.world);\n var back = false;\n if (dot(n, V) < 0.0) { n = -n; back = true; }\n let q = in.uv;\n let dbg = u32(k.ring.y);\n if ((dbg & 512u) != 0u) { return vec4f((*B).col0.rgb, 1.0); }\n let TT = cal_tangents(n, dPdx, dPdy, dqdx, dqdy);\n let bn = textureSampleLevel(noise_tex, noise_samp, (in.clip.xy + k.shadow2.yz) * u.noise.z + u.noise.xy, 0.0).rgb;\n let Lw = (*B).lamp.xyz; // a pad lies square on the desk: its own frame is the world's, turned by nothing\n let moving = mat == 1;\n let pp = cal_paper(k, (*B).col0.rgb, q, dqdx, dqdy, select(3.0, 17.0, moving), paper_tex, paper_samp);\n var albedo = pp.albedo;\n var marks = 0.0;\n var sheen = vec2f(5.0, 0.02);\n if (!back && moving && (dbg & 2u) == 0u) {\n let f = cal_face(pi, albedo, q, px, true, dbg);\n albedo = f.albedo;\n marks = f.marks;\n } else if (back) {\n // the back of a sheet: a shade duller — and a moving sheet's month just showing through it\n albedo *= 0.975;\n if (moving) {\n let through = cal_tile((*B).slots.y, q, px * 6.0, 0);\n albedo *= mix(vec3f(1.0), through.rgb, through.a * 0.06);\n }\n sheen = vec2f(4.0, 0.012);\n }\n // the shade of the rolls and the tape, on the flat of a sheet\n var sh = vec2f(1.0);\n if (!back && n.z > 0.985 && (dbg & 8u) == 0u) { sh = cal_sheet_shade(pi, in.local.xy, in.local.z, Lw, moving); }\n var c = cal_lit(albedo, n, TT, pp.g, in.world, V, Lw, sh.x, in.ao * sh.y, true, back, k.light.z, sheen, bn);\n c = mix(c, k.select.rgb, marks);\n return vec4f(c, 1.0);\n}\n\n// The pad's SOLID parts: the block's side (mat 3), the chipboard back (4), the cloth tape and its foil (5).\n@fragment\nfn fs_solid(in: VOut) -> @location(0) vec4f {\n let dPdx = dpdx(in.world);\n let dPdy = dpdy(in.world);\n let dqdx = dpdx(in.uv);\n let dqdy = dpdy(in.uv);\n let pi = u32(in.mat.z + 0.5);\n let B = &pads[pi];\n let px = max(max(length(dqdx), length(dqdy)), 1e-4);\n let mat = i32(in.mat.x + 0.5);\n let n = normalize(in.nrm);\n let E = vec3f(k.eye.x, k.eye.y, k.eye.z);\n let V = normalize(E - in.world);\n let q = in.uv;\n let TT = cal_tangents(n, dPdx, dPdy, dqdx, dqdy);\n let bn = textureSampleLevel(noise_tex, noise_samp, (in.clip.xy + k.shadow2.yz) * u.noise.z + u.noise.xy, 0.0).rgb;\n let Lw = (*B).lamp.xyz;\n var albedo = (*B).col0.rgb;\n var grad = vec2f(0.0);\n var dapple = k.light.z;\n var sheen = vec2f(4.0, 0.0);\n if (mat == 3) {\n // the BLOCK's side: the months to come, edge on — one stripe a sheet, each its own shade\n let t = max((*B).z.z - (*B).z.y, 1e-3);\n let f = q.y / t;\n let i = floor(f);\n let gg = fract(f);\n let vis2 = smoothstep(0.7, 2.2, t / px);\n let own = hash12(vec2f(i, 7.0)) - 0.5;\n let gap = pow(1.0 - min(gg, 1.0 - gg) * 2.0, 6.0);\n albedo = (*B).col0.rgb * (0.9 + own * 0.08 * vis2 - gap * 0.3 * vis2);\n } else if (mat == 4) {\n // the chipboard back: pressed fibre, flecked\n let fb = nb_fbm(q * 0.6);\n albedo = (*B).col5.rgb * (0.94 + fb.x * 0.12 + (nb_hash(vec2i(floor(q * 1.7))) - 0.5) * 0.06 * nb_detail(0.6, px));\n sheen = vec2f(3.0, 0.0);\n dapple = k.light.w;\n } else {\n // the TAPE: bookcloth, and the foil stamped on it (the sheet's print under the tape carries it)\n let cl = nb_cloth(q, k.cloth.x, px);\n albedo = (*B).col6.rgb * (1.0 + cl.shade * 0.16);\n grad = cl.g * k.cloth.y;\n let foil = cal_tile((*B).slots.x, q, px, 0);\n let f = foil.a * step(q.y, (*B).size.z);\n albedo = mix(albedo, (*B).col7.rgb * (0.92 + 0.16 * bn.z), f);\n sheen = vec2f(mix(k.cloth.z, 22.0, f), mix(k.cloth.w, 0.22, f));\n dapple = k.light.w;\n }\n let c = cal_lit(albedo, n, TT, grad, in.world, V, Lw, 1.0, in.ao, false, false, dapple, sheen, bn);\n return vec4f(c, 1.0);\n}\n\n// ---- the mat under a pad: its shadow, its contact, its ring\n\nstruct ROut {\n @builtin(position) clip: vec4f,\n @location(0) world: vec2f,\n @location(1) @interpolate(flat) pad: u32,\n}\n\nconst RQUAD = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 1.0));\n\n@vertex\nfn vs_recv(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> ROut {\n let B = &pads[iid];\n var out: ROut;\n let p = mix((*B).recv.xy, (*B).recv.zw, RQUAD[vid]);\n out.clip = cal_clip(k, vec3f(p, 0.0));\n out.world = p;\n out.pad = iid;\n return out;\n}\n\n@fragment\nfn fs_recv(in: ROut) -> @location(0) vec4f {\n let B = &pads[in.pad];\n let P = ((*B).inv * vec4f(in.world, 0.0, 1.0)).xyz;\n let Ll = normalize(((*B).inv * vec4f((*B).lamp.xyz, 0.0)).xyz);\n let half = (*B).size.xy * 0.5;\n let h = (*B).z.w + (*B).wipe2.z; // the pad's height, and its lift while carried\n // the slab's shadow: its footprint carried away from the lamp by its height, softened by it\n let shift = -Ll.xy / max(Ll.z, 1e-3) * h;\n let sigma = k.shadow.x + k.shadow.y * h;\n let dS = nb_sd_box(P.xy - shift * 0.5, half + abs(shift) * 0.5, 0.0);\n let slab = 1.0 - smoothstep(-sigma * 1.5, sigma * 1.5, dS);\n // the roll of the months before, lying along the head: its end throws a little shade past the pad's side\n let R0 = (*B).roll2.w;\n let o0 = vec2f(-half.x, -half.y + (*B).size.z + R0);\n let rv = cal_cyl_shadow(k, P.xy, 0.0, o0, vec2f(1.0, 0.0), R0, (*B).z.z + R0 + (*B).wipe2.z, Ll, 0.0, (*B).size.x);\n // a paper roll's end throws a light, soft shade (its ends are open, the lamp's light passes through the paper)\n let aS = k.shadow.z * max(slab, (1.0 - rv) * 0.45);\n // the contact where the pad meets the mat (gone as it lifts)\n let d = nb_sd_box(P.xy, half, 0.0);\n let touch = 1.0 - smoothstep(0.0, 2.5, (*B).wipe2.z);\n let s = k.shadow2.x;\n let aC = k.shadow.w * touch * exp(-max(d, 0.0) * max(d, 0.0) / (2.0 * s * s)) * step(-0.5, d);\n let a = 1.0 - (1.0 - aS) * (1.0 - aC);\n var c = vec4f(k.castCol.rgb * a, a);\n // the selection ring, on the mat around the pad\n let css = 1.0 / max(k.eye.w, 1e-6);\n let rw = k.select.w * css;\n let rd = abs(d - k.ring.x * css) - rw * 0.5;\n let ring = nb_cov(rd, css / max(k.shadow2.w, 1.0)) * (*B).slots.w;\n c = vec4f(k.select.rgb * ring, ring) + c * (1.0 - ring);\n return c;\n}\n";
10
+ readonly "calendar/calendar.wgsl": "// The DESK CALENDAR — a pad of months under the desk's one lamp (CALENDAR.md). A PURE module: the\n// uniform blocks and the pad's record arrive as parameters, and it names no colour. mat.wgsl and\n// notebook.wgsl come first: the light, the gobo, the night and the noise are the mat's; the weave,\n// the colour chain and the coverage helpers are the notebook's — the pad is the same paper and cloth.\n//\n// Here: the desk eye (the notebook's `nb_clip`, over this pass's knobs); the ROLL (roll.ts's\n// `rollPoint`, point for point — the moving sheet is bent in the vertex shader); the paper; the\n// GRID the month is printed on (its rules, the weekend's wash, a neighbour month's days — analytic,\n// so they are crisp at every zoom); and the rolls' shadows on the pad, cast by the lamp from a\n// cylinder lying on the paper (exact, so no shadow map is drawn for a flat thing).\n\nconst CAL_PI = 3.14159265;\n\nfn cal_clip(k: CalUniforms, 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// ---- the roll: a sheet point (x across, s down from the tape's edge) as the spiral bends it\nstruct CalRollPt { p: vec3f, n: vec3f, phi: f32 }\n\nfn cal_radius(r0: f32, tau: f32, D: f32) -> f32 { return sqrt(r0 * r0 + tau * max(D, 0.0) / CAL_PI); }\n\nfn cal_roll(a: f32, alpha: f32, px: f32, r0: f32, tau: f32, L: f32, x: f32, s: f32) -> CalRollPt {\n var out: CalRollPt;\n let sa = sin(alpha);\n let ca = cos(alpha);\n let d = (x - px) * (-sa) + (s - a) * ca;\n if (d <= 0.0) {\n out.p = vec3f(x, s, 0.0);\n out.n = vec3f(0.0, 0.0, 1.0);\n out.phi = 0.0;\n return out;\n }\n let D = max((x - px) * (-sa) + (L - a) * ca, d);\n let R = cal_radius(r0, tau, D);\n var phi = d / R;\n if (tau > 1e-6) { phi = (2.0 * CAL_PI / tau) * (R - sqrt(max(R * R - tau * d / CAL_PI, 0.0))); }\n let rho = R - tau * phi / (2.0 * CAL_PI);\n let n2 = vec2f(-sa, ca);\n let b = vec2f(x, s) - d * n2;\n let sp = sin(phi);\n let cp = cos(phi);\n out.p = vec3f(b + n2 * (rho * sp), R - rho * cp);\n out.n = vec3f(-n2 * sp, cp);\n out.phi = phi;\n return out;\n}\n\n// ---- the paper (the notebook's baked texture, over this pass's knobs)\nstruct CalPaperS { albedo: vec3f, g: vec2f }\nfn cal_paper(k: CalUniforms, base: vec3f, q: vec2f, dqdx: vec2f, dqdy: vec2f, seed: f32, tex: texture_2d<f32>, samp: sampler) -> CalPaperS {\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: CalPaperS;\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 grid the month is printed on: the rules' coverage, the heading rule's, the weekend's wash, a neighbour's day\nstruct CalGrid { rule: f32, head: f32, weekend: f32, outside: f32 }\n\n// A thin line's coverage at distance `d` (world units) of half-width `hw`, never thinner than a device px's worth of ink.\nfn cal_line(d: f32, hw: f32, px: f32) -> f32 {\n let w = max(hw, px * 0.5);\n return nb_sat(0.5 - (abs(d) - w) / max(px, 1e-5)) * min(hw / w, 1.0);\n}\n\nfn cal_grid(k: CalUniforms, q: vec2f, px: f32, G0: vec4f, G1: vec4f) -> CalGrid {\n var out: CalGrid;\n let rows = G1.x;\n let gw = G0.z * 7.0;\n let gh = G0.w * rows;\n let p = q - G0.xy;\n let inside = step(-px, p.x) * step(p.x, gw + px) * step(-px, p.y) * step(p.y, gh + px);\n // the nearest rule down and across\n let cx = p.x / G0.z;\n let cy = p.y / G0.w;\n let dx = (cx - round(cx)) * G0.z;\n let dy = (cy - round(cy)) * G0.w;\n let hw = k.ring.z * 0.5;\n out.rule = max(cal_line(dx, hw, px), cal_line(dy, hw, px)) * inside;\n out.head = cal_line(p.y, k.ring.w * 0.5, px) * step(-px, p.x) * step(p.x, gw + px);\n let col = i32(floor(cx));\n let row = i32(floor(cy));\n let inCell = step(0.0, p.x) * step(p.x, gw) * step(0.0, p.y) * step(p.y, gh);\n let mask = u32(G1.y + 0.5);\n out.weekend = select(0.0, 1.0, col >= 0 && col < 7 && ((mask >> u32(clamp(col, 0, 6))) & 1u) != 0u) * inCell;\n let idx = row * 7 + col;\n let lead = i32(G1.z + 0.5);\n let days = i32(G1.w + 0.5);\n out.outside = select(0.0, 1.0, idx < lead || idx >= lead + days) * inCell;\n return out;\n}\n\n// ---- a lying cylinder's shadow on the paper: the lamp's ray from a point at height `zr` past an axis\n// at height `za` (its radius R, lying along `dir` through `o` in the pad's plane): how much light gets\n// through (1 lit … 0 shadowed), softened by the desk's penumbra law for the gap\nfn cal_cyl_shadow(k: CalUniforms, P: vec2f, zr: f32, o: vec2f, dir: vec2f, R: f32, za: f32, L: vec3f, x0: f32, x1: f32) -> f32 {\n if (R <= 0.0) { return 1.0; }\n let t = dot(P - o, dir);\n let nrm = vec2f(-dir.y, dir.x);\n let dn = dot(P - o, nrm);\n // the ray in the plane across the axis: from (dn, zr) toward the lamp (ln, lz)\n let ln = dot(L.xy, nrm);\n let lz = max(L.z, 1e-3);\n let rel = vec2f(0.0 - dn, za - zr);\n let dirL = normalize(vec2f(ln, lz));\n let along = dot(rel, dirL);\n if (along <= 0.0) { return 1.0; }\n let miss = abs(rel.x * dirL.y - rel.y * dirL.x);\n let gap = max(za - zr, 0.0);\n let sigma = k.shadow.x + k.shadow.y * gap;\n let s = smoothstep(R - sigma * 1.2, R + sigma * 1.2, miss);\n // along the axis: the roll ends where the sheet does (its shadow's ends soft too)\n let ta = t + dot(L.xy, dir) / lz * (za - zr);\n let ends = smoothstep(x0 - sigma, x0 + sigma, ta) * (1.0 - smoothstep(x1 - sigma, x1 + sigma, ta));\n return 1.0 - (1.0 - s) * ends;\n}\n\n// The crease where a roll meets the paper: a little dark each side of its line of contact.\nfn cal_crease(P: vec2f, o: vec2f, dir: vec2f, R: f32) -> f32 {\n if (R <= 0.0) { return 1.0; }\n let dn = dot(P - o, vec2f(-dir.y, dir.x));\n let w = max(R * 0.45, 1.0);\n return 1.0 - 0.32 * exp(-(dn * dn) / (w * w));\n}\n\n// A box's outline, `w` wide, just inside it: the marks the host draws on the sheet.\nfn cal_outline(q: vec2f, box: vec4f, w: f32, r: f32, px: f32) -> f32 {\n if (box.z <= box.x) { return 0.0; }\n let c = (box.xy + box.zw) * 0.5;\n let h = (box.zw - box.xy) * 0.5;\n let d = nb_sd_box(q - c, h - vec2f(w * 0.5 + 1.0), r);\n return nb_cov(abs(d) - w * 0.5, px);\n}\n\n// The selection's BRACKETS on a box of the sheet (D3t-c — *Marks on the Mat* Q-a on a day or a line): the outline strong along\n// `reach` of each side from every corner (never past 30 % of a side), the hairline between them one device px at 42 %.\nfn cal_brackets(q: vec2f, box: vec4f, w: f32, r: f32, px: f32, reach: f32) -> f32 {\n if (box.z <= box.x) { return 0.0; }\n let c = (box.xy + box.zw) * 0.5;\n let h = (box.zw - box.xy) * 0.5;\n let a = abs(q - c);\n let arm = min(vec2f(reach), h * 0.3 + vec2f(r + 2.0));\n let arms = nb_sat((a.x - (h.x - arm.x)) / max(px, 1e-5) + 0.5) * nb_sat((a.y - (h.y - arm.y)) / max(px, 1e-5) + 0.5);\n let hair = nb_cov(abs(nb_sd_box(q - c, h - vec2f(w * 0.5 + 1.0), r)) - 0.5 * px, px);\n return max(cal_outline(q, box, w, r, px) * arms, hair * 0.42);\n}\n";
11
+ readonly "minimat/minimat-pass.wgsl": "// The mini mat pass (MINIMAT.md §6) — the desk's containers, instanced: one quad per mini mat,\n// expanded on the vertex side by its shadow's reach and the AA. Two entries on one layout:\n//\n// fs the mini mat: its shadow, the vinyl with its print and its cut edge, the selection\n// ring — and, while no live inside covers it, the FACE's far LOD: the inside's lattice\n// and its children as CHIPS (a note's writing greeked). Where a live inside is drawn\n// (the ground draws it right after the mini mat — ground.ts `drawSlot`) the face is\n// plain vinyl under it: the inside's own mat covers it, the same lattice by the same lamp.\n// fs_chips the chips again, OVER the live inside, at 1 − its presence — the far LOD fading out\n// as the inside's objects fade in (MINIMAT.md §5), drawn only while both are on.\n//\n// Premultiplied, through the presentation's opacity and the portal clip.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> k: MiniMatUniforms;\n@group(0) @binding(2) var<storage, read> mats: array<MiniMat>;\n@group(0) @binding(3) var<storage, read> chips: array<Chip>;\n@group(0) @binding(4) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(5) var gobo_samp: sampler;\n@group(0) @binding(6) var noise_tex: texture_2d<f32>;\n@group(0) @binding(7) var noise_samp: sampler;\n@group(0) @binding(8) var glyph_tex: texture_2d<f32>; // the rulers' glyph atlas and the capitals (RULER.md, MINIMAT.md §2)\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\nconst CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.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.clip = vec4f(2.0, 2.0, 2.0, 1.0); // degenerate: a culled instance collapses\n let slot = order[iid];\n out.idx = slot;\n let M = &mats[slot];\n let zoom = mat_zoom(u);\n let px = 1.0 / (zoom * mat_dpr(u));\n // the sheet's bounds, grown by everything the fragment can paint outside it: the slab's sweep, its penumbra, the contact, the AA\n let top = (*M).lift + (*M).thick;\n let pad = length((*M).slope) * top + 2.5 * (k.shadow.x + k.shadow.y * top) + 2.5 * k.shadow2.x + 3.0 * px;\n let lo = ((*M).centre - (*M).half - vec2f(pad) - u.cam.xy) * zoom; // CSS px\n let hi = ((*M).centre + (*M).half + vec2f(pad) - u.cam.xy) * zoom;\n if (hi.x < u.box.x || hi.y < u.box.y || lo.x > u.box.x + u.box.z || lo.y > u.box.y + u.box.w) { return out; }\n let pos = mix(lo, hi, CORNERS[vid] * 0.5 + 0.5);\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// What a shadow is made of: the world's darkest value by day (the design's cast), the dark's own grey by night.\nfn shadow_ink() -> vec3f { return mix(k.castTint.rgb, mm_encode(u.eigengrau.xyz), clamp(u.night.x, 0.0, 1.0)); }\n\n// The children as CHIPS (MINIMAT.md §5), composited over `acc` (premultiplied): each flat on the\n// face in its own colour, its contact shadow cast along the lamp's slope by its height, a note's\n// writing greeked into strokes of ink along its lines' x-height once the chip is tall enough to\n// read as writing, a mini mat of its own wearing its frame line. `s` is the inside's scale.\nfn chips_on(M: MiniMat, p: vec2f, px: f32, css: f32, s: f32, gobo: f32, bn: vec3f, ink0: vec3f, grain3: vec3f, acc0: vec4f) -> vec4f {\n var acc = acc0;\n let S = M.slope;\n for (var i = 0u; i < M.chipCount; i++) {\n let C = &chips[M.chipFirst + i];\n let h = (*C).colour.w;\n let cr = (*C).rot;\n let ch = (*C).half;\n // the tilted chip's bounds, grown by its shadow's reach and the AA\n let ext = vec2f(abs(cr.x) * ch.x + abs(cr.y) * ch.y, abs(cr.y) * ch.x + abs(cr.x) * ch.y) + vec2f(length(S) * h + 3.0 * max(h, px) + 2.0 * px);\n let rel = p - (*C).centre;\n if (abs(rel.x) > ext.x || abs(rel.y) > ext.y) { continue; }\n let lq = vec2f(cr.x * rel.x + cr.y * rel.y, -cr.y * rel.x + cr.x * rel.y);\n let dc = sdf_round_box(lq, ch, (*C).radius);\n let rs = rel - S * h;\n let ls = vec2f(cr.x * rs.x + cr.y * rs.y, -cr.y * rs.x + cr.x * rs.y);\n let shc = mm_blur(sdf_round_box(ls, ch, (*C).radius), max(0.8 * h, 0.75 * px)) * 0.35 * (1.0 - mm_cov(dc, px));\n acc = mm_over(vec4f(ink0 * shc, shc), acc);\n if (dc > px) { continue; }\n let cc = mm_cov(dc, px);\n var lit = vec3f(0.0);\n if ((*C).kind > 0.5) {\n // a mini mat of its own: its vinyl with the grain, and its frame line just outside its face — never thinner than a device px\n let fm = (*C).frame.x;\n let w = max(k.print.w * s, px);\n let fr = mm_line(sdf_box(lq, max(ch - vec2f(fm), vec2f(1.0e-3))) - w, w, px) * k.print.x * min(k.print.w * s / px, 1.0) * 1.6;\n lit = mm_vinyl(u, mix((*C).colour.rgb + grain3, k.cream.rgb, min(fr, 1.0)), 1.0, gobo, bn.y);\n } else {\n var pa = (*C).colour.rgb;\n let n = u32((*C).frame.y);\n let tall = 2.0 * ch.y / css;\n let greek = smoothstep(0.6 * k.cream.w, k.cream.w, tall) * k.shadow2.z;\n if (greek > 0.0 && n > 0u) {\n var wl = 0.0;\n for (var j = 0u; j < n; j++) {\n let L = (*C).lines[j];\n wl = max(wl, mm_cov(sdf_segment(lq, vec2f(L.x, L.z), vec2f(L.y, L.z)) - (*C).ink.w, px));\n }\n pa = mix(pa, (*C).ink.rgb, wl * greek);\n }\n lit = mm_paper(u, pa, gobo, bn.y);\n }\n acc = mm_over(vec4f(lit * cc, cc), acc);\n }\n return acc;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let M = mats[in.idx];\n let dpr = mat_dpr(u);\n let zoom = mat_zoom(u);\n let px = 1.0 / (zoom * dpr); // world per device px\n let css = px * dpr; // world per CSS px\n let p = in.clip.xy * px + u.cam.xy; // device px → world\n let q = p - M.centre;\n let d = sdf_round_box(q, M.half, M.radius);\n let ink0 = shadow_ink();\n let clip = M.alpha * u.view.w * portal_cover(in.clip.xy / dpr, u.portals, u.clips, dpr);\n\n // The slab's shadow on the desk, outside the sheet.\n var acc = vec4f(0.0);\n if (d > -px) {\n let sh = mm_slab(q, M.half, M.radius, M.lift, M.lift + M.thick, M.slope, k) * (1.0 - mm_cov(d, px));\n acc = vec4f(ink0 * sh, sh);\n }\n if (d > px) { return acc * clip; }\n let cov = mm_cov(d, px);\n\n // The vinyl: the mat's own sage and grain (mat.wgsl `mat_albedo`'s — screen-anchored, as the desk's is).\n let frag = in.clip.xy;\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n var vinyl = M.ground.xyz;\n let amp = M.ground.w;\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 vinyl += vec3f(g * 0.4, g, g * 0.35);\n\n // The inside's point under this fragment (host = o + inside · s), and inside units per device px.\n let s = max(M.inside.x, 1.0e-9);\n let a = (p - M.inside.yz) / s;\n let ipx = px / s;\n let fh = max(M.half - vec2f(M.margin), vec2f(1.0e-3));\n let inFace = max(abs(q.x) - fh.x, abs(q.y) - fh.y) < 0.0;\n let faceOwn = inFace && M.live < 0.0; // the face is drawn here only where no live inside covers it\n\n // The lattice on the face, the print in the border — both the mat's cream, mixed in before the light.\n var lines = 0.0;\n if (faceOwn) { lines = mm_lattice(M, a, ipx); }\n let ink = mm_print(u, M, k, q, fh, a, px, ipx, zoom, glyph_tex, gobo_samp);\n let albedo = mix(mix(vinyl, k.cream.rgb, lines), k.cream.rgb, ink);\n\n // The light: the desk's lamp where the slot's light sees this point (MINIMAT.md §4) — the desk plane's\n // own dapple, as a live inside's mat takes it, so the face and the inside are lit alike; the cut edge's roll.\n let gobo = lit_gobo(u, gobo_tex, gobo_samp, p, 0.0, desk_of(u, p), bn.z);\n let bevel = mm_bevel(q, d, M, k);\n var col = vec4f(mm_vinyl(u, albedo, bevel, gobo, bn.y) * cov, cov);\n if (faceOwn && M.chipCount > 0u) { col = chips_on(M, p, px, css, s, gobo, bn, ink0, vinyl - M.ground.xyz, col); }\n\n // The sole-selection ring, inside the rim, in the theme's select.\n let ring = mm_line(d, k.edge.w * css, px) * M.ring;\n col = mm_over(vec4f(k.select.rgb * ring, ring), col);\n acc = mm_over(col, acc);\n return acc * clip;\n}\n\n@fragment\nfn fs_chips(in: VSOut) -> @location(0) vec4f {\n let M = mats[in.idx];\n if (M.live < 0.0 || M.live >= 1.0 || M.chipCount == 0u) { discard; }\n let fade = 1.0 - M.live;\n let dpr = mat_dpr(u);\n let px = 1.0 / (mat_zoom(u) * dpr);\n let p = in.clip.xy * px + u.cam.xy;\n let q = p - M.centre;\n let fh = max(M.half - vec2f(M.margin), vec2f(1.0e-3));\n if (max(abs(q.x) - fh.x, abs(q.y) - fh.y) >= 0.0) { discard; }\n let bn = textureSampleLevel(noise_tex, noise_samp, in.clip.xy * u.noise.z + u.noise.xy, 0.0).rgb;\n let gobo = lit_gobo(u, gobo_tex, gobo_samp, p, 0.0, desk_of(u, p), bn.z);\n let frag = in.clip.xy;\n let amp = M.ground.w;\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 let acc = chips_on(M, p, px, px * dpr, max(M.inside.x, 1.0e-9), gobo, bn, shadow_ink(), vec3f(g * 0.4, g, g * 0.35), vec4f(0.0));\n if (acc.a < 0.002) { discard; }\n return acc * (fade * M.alpha * u.view.w * portal_cover(in.clip.xy / dpr, u.portals, u.clips, dpr));\n}\n";
12
+ readonly "minimat/minimat.wgsl": "// THE MINI MAT (MINIMAT.md) — a smaller self-healing cutting mat lying on the desk, and inside\n// it another desk. A PURE module: the record `MiniMat`, the pass's knobs `MiniMatUniforms` and\n// the mat's block `MatUniforms` arrive as parameters; it names no colour.\n//\n// The sheet is the mat's own vinyl, so its colour goes through the mat's own chain (`mat_colour`\n// — the reference's double gamma by day, the Moon by night) and it takes the dapple of the lamp\n// the desk is lit by. What tells it from the desk is what a small mat on a big one shows: its\n// shadow — a thin slab's, the board's sweep with a contact line at its base; its CUT EDGE — a\n// bevel that catches the lamp on the near side and falls away on the far; and its PRINT, in the\n// mat's cream — a frame round the cutting area; a ruler along the face's top and left edges, its\n// ticks at the very lattice the face shows (minor · medium · major for its fine, mid and coarse\n// rungs) and numbered in the inside's own units with the rulers' own digits (ruler.wgsl); and the\n// mat's NAME in the foot, in the design's print label (mono capitals, tracked), as a mat's maker\n// prints its own. The FACE, at the far LOD, is the inside's\n// lattice as the live inside would draw it (the rungs and their weights come from the host,\n// dressed as the live slot dresses its mat), and its children are drawn over it as CHIPS by the\n// entry: flat silhouettes in their own colours, a note's writing greeked into lines of ink.\n\nfn mm_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\n// A band `w` wide INSIDE the edge d = 0.\nfn mm_line(d: f32, w: f32, px: f32) -> f32 { return mm_cov(d, px) - mm_cov(d + w, px); }\nfn mm_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage — a shadow, straight out of the field.\nfn mm_blur(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * mm_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n// Premultiplied \"src over dst\".\nfn mm_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n// A world point raised `h` world units off the desk, in the projector's frame (mat.wgsl `desk_of` with a height).\nfn mm_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\nfn mm_encode(c: vec3f) -> vec3f {\n let x = clamp(c, vec3f(0.0), vec3f(1.0));\n return mix(x * 12.92, 1.055 * pow(x, vec3f(1.0 / 2.4)) - 0.055, step(vec3f(0.0031308), x));\n}\n\n// The shadow a THIN SLAB casts (the board's `bd_slab`): its footprint swept along the ground slope\n// `S` from its underside `bottom` to its top `top`, each copy blurred by its own height — the union\n// as the max — and a contact line at its base while it rests, fading as it floats off.\nfn mm_slab(q: vec2f, half: vec2f, r: f32, bottom: f32, top: f32, S: vec2f, k: MiniMatUniforms) -> f32 {\n var sh = 0.0;\n for (var i = 0; i < 4; i++) {\n let h = mix(bottom, top, f32(i) / 3.0);\n if (h <= 0.0) { continue; }\n sh = max(sh, mm_blur(sdf_round_box(q - S * h, half, r), k.shadow.x + k.shadow.y * h));\n }\n sh = sh * k.shadow.z;\n let g = max(bottom, 0.0);\n let con = mm_blur(sdf_round_box(q - S * min(g, 1.0) * 0.5, half, r), k.shadow2.x) * k.shadow.w * exp(-g / max(k.shadow2.y, 1.0e-3));\n return max(sh, con);\n}\n\n// The CUT EDGE: within `k.edge.x` of the rim the sheet's surface rolls over — turned toward the\n// lamp it brightens, turned away it darkens. `q` in the sheet's frame, `d` its distance; a factor\n// on the linear albedo (1 on the flat).\nfn mm_bevel(q: vec2f, d: f32, M: MiniMat, k: MiniMatUniforms) -> f32 {\n let t = smoothstep(-k.edge.x, 0.0, d);\n if (t <= 0.0) { return 1.0; }\n // the rim's outward normal: along the corner's radius, or the nearer side's axis\n let e = abs(q) - (M.half - vec2f(M.radius));\n var n = vec2f(0.0);\n if (e.x > 0.0 && e.y > 0.0) { n = normalize(e) * sign(q); }\n else if (e.x > e.y) { n = vec2f(sign(q.x), 0.0); }\n else { n = vec2f(0.0, sign(q.y)); }\n let facing = dot(n, M.lamp);\n return 1.0 + t * select(facing * k.edge.z, facing * k.edge.y, facing > 0.0);\n}\n\n// The face's own lattice at the far LOD (mat.wgsl `grid2` per rung, weighed as the live inside\n// weighs it): `a` the inside point, `ipx` inside units per DEVICE px.\nfn mm_lattice(M: MiniMat, a: vec2f, ipx: f32) -> f32 {\n var cover = 0.0;\n if (M.weights.x > 0.0) { cover = grid2(a, M.rungs.x, M.widths.x * ipx, ipx) * M.weights.x; }\n if (M.weights.y > 0.0) { cover = max(cover, grid2(a, M.rungs.y, M.widths.y * ipx, ipx) * M.weights.y); }\n if (M.weights.z > 0.0) { cover = max(cover, grid2(a, M.rungs.z, M.widths.z * ipx, ipx) * M.weights.z); }\n return cover;\n}\n\n// One rung's ticks along one edge: the site's line across the edge (`along` the inside coordinate\n// along it, `ipx` inside units per device px, `hw` the print's half-width in inside units), standing\n// out of the face by `len` world units (`out` how far out the point is, world; `px` world per device px).\nfn mm_tick(along: f32, spacing: f32, hw: f32, ipx: f32, out: f32, len: f32, px: f32) -> f32 {\n return line_coverage(along, spacing, hw, ipx) * mm_cov(out - len, px) * mm_cov(-out, px);\n}\n\n// A texel of the glyph atlas (RULER.md; mat/layout.ts `GLYPHS`): glyph `g` at `tx` texels from its\n// origin (its cell's left edge + the pad) and `ty` texels down from its cell's top; 0 outside it.\nfn mm_glyph(tex: texture_2d<f32>, samp: sampler, k: MiniMatUniforms, g: i32, tx: f32, ty: f32) -> f32 {\n if (g < 0 || f32(g) >= k.atlas2.w || tx < -GLYPH_PAD || tx >= k.atlas.x - GLYPH_PAD || ty < 0.0 || ty >= k.atlas.y) { return 0.0; }\n return textureSampleLevel(tex, samp, vec2f(f32(g) * k.atlas.x + GLYPH_PAD + tx, ty) / k.atlas2.yz, 0.0).r;\n}\n\n// A NUMERAL along one edge: `A` the inside coordinate along it at the fragment, `host` the fragment's\n// host coordinate along it and `o` the host coordinate of inside 0 (the embedding's offset on that\n// axis); `acrossOuter` how far in from the sheet's outer edge (world); `turned` the left edge (the\n// glyph turned a quarter clockwise, read down, its top toward the face — the desk rulers' hand).\n// The label is the site at or before the fragment: `index · mult · 10^exp` (ruler.wgsl `ruler_label`).\nfn mm_numeral(M: MiniMat, k: MiniMatUniforms, tex: texture_2d<f32>, samp: sampler, A: f32, host: f32, o: f32, acrossOuter: f32, turned: bool) -> f32 {\n let S = M.numerals.x;\n if (S <= 0.0 || k.atlas2.w <= 0.0) { return 0.0; }\n let s = max(M.inside.x, 1.0e-9);\n let n = floor(A / S);\n let index = i32(n);\n let presence = select(M.numerals.w, 1.0, index % 10 == 0);\n if (presence <= 0.0) { return 0.0; }\n let tpw = k.atlas2.x / max(k.digits.x, 1.0e-6); // atlas texels per world unit, the cap on the digits' cap\n let adv = k.atlas.z / tpw; // a digit's advance, world\n let t = host - (o + n * S * s) - k.digits.y; // world along, after the tick and the gap\n if (t < 0.0) { return 0.0; }\n let slot = i32(floor(t / adv));\n let g = ruler_label(index, i32(M.numerals.y), i32(M.numerals.z), slot);\n if (g < 0) { return 0.0; }\n var v = 0.0;\n if (turned) { v = k.atlas.w - (acrossOuter - k.digits.z) * tpw; }\n else { v = (acrossOuter - (k.digits.z - (k.atlas.w - k.atlas2.x) / tpw)) * tpw; }\n return mm_glyph(tex, samp, k, g, (t - f32(slot) * adv) * tpw, v) * presence;\n}\n\n// The NAME's glyph at slot `i` (minimat.ts `packGlyphs`: four 6-bit indices to a float).\nfn mm_name_glyph(M: MiniMat, i: i32) -> i32 {\n let f = M.name[i / 16][(i / 4) % 4];\n return i32((u32(f) >> (6u * u32(i % 4))) & 63u);\n}\n\n// The PRINT in the border, as a presence of the cream: the frame just outside the face; the ruler\n// along its top and left edges — ticks on the inside's lattice (each rung at its line's presence,\n// relative to the law's thickest: a tick is the line it stands for, printed on) and the numerals; and\n// the NAME in the foot, from the face's left edge. A line never prints thinner than a device px (the\n// ink thins instead); the words print only once their cap is legible. `q` in the sheet's frame, `fh`\n// the face's half extents, `a` the inside point, `zoom` the camera's.\nfn mm_print(u: MatUniforms, M: MiniMat, k: MiniMatUniforms, q: vec2f, fh: vec2f, a: vec2f, px: f32, ipx: f32, zoom: f32, tex: texture_2d<f32>, samp: sampler) -> f32 {\n let w = max(k.print.w, px);\n let thin = min(k.print.w / px, 1.0);\n let dF = sdf_box(q, fh);\n // the frame: a band hugging the face from outside (the live inside is clipped to the face, so it never covers it)\n var ink = mm_line(dF - w, w, px) * k.print.x * thin;\n if (dF <= 0.0) { return ink; }\n // the ruler's ticks: out of the top edge (−y) and the left (−x)\n let hw = 0.5 * w / max(M.inside.x, 1.0e-9);\n let thick = max(u.line.w, 1.0e-3);\n let outTop = -fh.y - q.y;\n let outLeft = -fh.x - q.x;\n var t = 0.0;\n if (outTop > -px && q.x > -fh.x - px && q.x < fh.x + px) {\n if (M.weights.x > 0.0) { t = max(t, mm_tick(a.x, M.rungs.x, hw, ipx, outTop, k.ticks.x, px) * min(M.weights.x / thick, 1.0)); }\n if (M.weights.y > 0.0) { t = max(t, mm_tick(a.x, M.rungs.y, hw, ipx, outTop, k.ticks.y, px) * min(M.weights.y / thick, 1.0)); }\n if (M.weights.z > 0.0) { t = max(t, mm_tick(a.x, M.rungs.z, hw, ipx, outTop, k.ticks.z, px) * min(M.weights.z / thick, 1.0)); }\n }\n if (outLeft > -px && q.y > -fh.y - px && q.y < fh.y + px) {\n if (M.weights.x > 0.0) { t = max(t, mm_tick(a.y, M.rungs.x, hw, ipx, outLeft, k.ticks.x, px) * min(M.weights.x / thick, 1.0)); }\n if (M.weights.y > 0.0) { t = max(t, mm_tick(a.y, M.rungs.y, hw, ipx, outLeft, k.ticks.y, px) * min(M.weights.y / thick, 1.0)); }\n if (M.weights.z > 0.0) { t = max(t, mm_tick(a.y, M.rungs.z, hw, ipx, outLeft, k.ticks.z, px) * min(M.weights.z / thick, 1.0)); }\n }\n ink = max(ink, t * k.print.y * thin);\n // the words, once their cap is legible on screen\n let digitsOn = smoothstep(k.text.z, k.text.w, k.digits.x * zoom);\n if (digitsOn > 0.0) {\n if (outTop > 0.0 && q.x > -fh.x && q.x < fh.x) { ink = max(ink, mm_numeral(M, k, tex, samp, a.x, q.x + M.centre.x, M.inside.y, q.y + M.half.y, false) * k.print.y * digitsOn); }\n if (outLeft > 0.0 && q.y > -fh.y && q.y < fh.y) { ink = max(ink, mm_numeral(M, k, tex, samp, a.y, q.y + M.centre.y, M.inside.z, q.x + M.half.x, true) * k.print.y * digitsOn); }\n }\n let nameOn = smoothstep(k.text.z, k.text.w, k.text.x * zoom);\n if (nameOn > 0.0 && M.nameLen > 0.0 && q.y > fh.y) {\n let tpw = k.atlas2.x / max(k.text.x, 1.0e-6);\n // a mono face's cap is ~0.72 of its em: the tracking is in em\n let adv = k.atlas.z / tpw + k.text.y * k.text.x / 0.72;\n let x = q.x + fh.x; // world from the face's left edge\n let baseline = 0.5 * (fh.y + M.half.y) + 0.5 * k.text.x; // the cap centred in the foot\n if (x >= 0.0 && x < 2.0 * fh.x) {\n let slot = i32(floor(x / adv));\n if (f32(slot) < M.nameLen) {\n let g = mm_name_glyph(M, slot);\n let v = (q.y - baseline) * tpw + k.atlas.w;\n ink = max(ink, mm_glyph(tex, samp, k, g, (x - f32(slot) * adv) * tpw, v) * k.print.y * nameOn);\n }\n }\n }\n return ink;\n}\n\n// The vinyl's colour on screen under the desk's light: its albedo (sRGB, the print mixed in) through\n// the mat's own chain — the same double gamma the desk's sage takes, the Moon by night — with the\n// cut edge's roll on the linear value.\nfn mm_vinyl(u: MatUniforms, albedo: vec3f, bevel: f32, gobo: f32, noise: f32) -> vec3f {\n return mat_colour(u, srgb_to_linear(clamp(albedo, vec3f(0.0), vec3f(1.0))) * bevel, gobo, noise);\n}\n\n// Paper on the face (a chip): the note's chain — the reference's grade on the sRGB albedo by day\n// (lit, the identity), the eye's chain on its linear value by night.\nfn mm_paper(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";
13
+ readonly "notebook/notebook-pass.wgsl": "// The notebook pass's entries (NOTEBOOK.md §6). Three draws per book into a 4× multisampled\n// layer, then one composite onto the canvas:\n// vs_shadow the book's depth from its lamp, into its layer of the shadow map\n// vs_book · fs_book the book itself — depth-tested, opaque, its page corners rounded by the sample mask\n// vs_recv · fs_recv the mat under it: the book's shadow (PCSS) and its contact, the selection ring —\n// drawn after the book, so the depth test skips every pixel the book covers\n// The book's record carries its placement, its lamp and its shadow map's frame.\n// The view block `u` is the SLOT's since K4a (design-016 K-L3), shared with the mat and every kind: this layer is drawn at FULL\n// presence, as it always was (the pass's own copy of the block carried no presentation, and the composite lays the layer whole),\n// so it reads neither `u.view.w` (the objects' presence) nor `u.presence.x` (the slot's) — its alpha is its coverage alone.\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> k: NbUniforms;\n@group(0) @binding(2) var<storage, read> books: array<NbBook>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>;\n@group(0) @binding(4) var gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var noise_samp: sampler;\n@group(0) @binding(7) var shadow_tex: texture_depth_2d_array;\n@group(0) @binding(8) var shadow_cmp: sampler_comparison;\n@group(0) @binding(9) var ink_tex: texture_2d_array<f32>; // the pages' ink rasters (straight alpha)\n@group(0) @binding(10) var ink_samp: sampler;\n@group(0) @binding(11) var paper_tex: texture_2d<f32>; // the baked paper (mottle · fibres · the tooth's slope), mipmapped\n@group(0) @binding(12) var paper_samp: sampler; // repeat, trilinear, anisotropic\n\n// The layer a page's ink is in, from the book's table; −1 when the page has none on the device. Read\n// straight from the storage buffer: indexing the arrays of a by-value record would push the whole\n// record out of registers.\nfn nb_ink_layer(bi: u32, page: f32) -> i32 {\n let pa = books[bi].inkPage[0];\n let pb = books[bi].inkPage[1];\n let hitA = (abs(pa - vec4f(page)) < vec4f(0.5)) & (pa >= vec4f(0.0));\n let hitB = (abs(pb - vec4f(page)) < vec4f(0.5)) & (pb >= vec4f(0.0));\n if (any(hitA)) { return i32(dot(select(vec4f(0.0), books[bi].inkLayer[0], hitA), vec4f(1.0)) + 0.5); }\n if (any(hitB)) { return i32(dot(select(vec4f(0.0), books[bi].inkLayer[1], hitB), vec4f(1.0)) + 0.5); }\n return -1;\n}\n\nstruct VIn {\n @location(0) pos: vec3f,\n @location(1) nrm: vec3f,\n @location(2) uv: vec2f,\n @location(3) mat: vec4f, // material, page, flags, ao\n @location(4) size: vec2f, // the face's size, world units\n @location(5) extra: vec2f,\n}\n\nstruct VOut {\n @builtin(position) clip: vec4f,\n @location(0) world: vec3f,\n @location(1) nrm: vec3f,\n @location(2) uv: vec2f,\n @location(3) @interpolate(flat) mat: vec3f,\n @location(4) ao: f32,\n @location(5) @interpolate(flat) size: vec4f,\n @location(6) @interpolate(flat) book: u32,\n}\n\n@vertex\nfn vs_shadow(@location(0) pos: vec3f, @builtin(instance_index) iid: u32) -> @builtin(position) vec4f {\n let B = books[iid];\n return B.light * (B.model * vec4f(pos, 1.0));\n}\n\n@vertex\nfn vs_book(v: VIn, @builtin(instance_index) iid: u32) -> VOut {\n let B = books[iid];\n var out: VOut;\n let P = (B.model * vec4f(v.pos, 1.0)).xyz;\n out.clip = nb_clip(k, P);\n out.world = P;\n out.nrm = (B.model * vec4f(v.nrm, 0.0)).xyz;\n out.uv = v.uv;\n out.mat = v.mat.xyz;\n out.ao = v.mat.w;\n out.size = vec4f(v.size, v.extra);\n out.book = iid;\n return out;\n}\n\n@fragment\nfn fs_book(in: VOut) -> @location(0) vec4f {\n // every derivative first — they must be taken in uniform control flow\n let dPdx = dpdx(in.world);\n let dPdy = dpdy(in.world);\n let dUdx = dpdx(in.uv);\n let dUdy = dpdy(in.uv);\n let dqdx = dUdx * in.size.xy;\n let dqdy = dUdy * in.size.xy;\n let B = books[in.book];\n let px = max(max(length(dPdx), length(dPdy)), 1e-4); // world units per device px\n let mat = i32(in.mat.x + 0.5);\n let flags = u32(in.mat.z + 0.5);\n var n = normalize(in.nrm);\n let E = vec3f(k.eye.x, k.eye.y, k.eye.z);\n let V = normalize(E - in.world);\n var page = in.mat.y;\n let q = in.uv * in.size.xy;\n var inside = false;\n if ((flags & 1u) != 0u && dot(n, V) < 0.0) { n = -n; page += 1.0; inside = true; }\n\n // the face's tangents (world units along its u and v) from the uv's screen derivatives\n let det = dUdx.x * dUdy.y - dUdx.y * dUdy.x;\n var Tu = vec3f(1.0, 0.0, 0.0);\n var Tv = vec3f(0.0, 1.0, 0.0);\n if (abs(det) > 1e-14) {\n Tu = (dPdx * dUdy.y - dPdy * dUdx.y) / det;\n Tv = (dPdy * dUdx.x - dPdx * dUdy.x) / det;\n }\n Tu = Tu - n * dot(n, Tu);\n Tv = Tv - n * dot(n, Tv);\n Tu = select(vec3f(1.0, 0.0, 0.0), normalize(Tu), length(Tu) > 1e-8);\n Tv = select(vec3f(0.0, 1.0, 0.0), normalize(Tv), length(Tv) > 1e-8);\n\n let bn = textureSampleLevel(noise_tex, noise_samp, (in.clip.xy + k.ring.zw) * u.noise.z + u.noise.xy, 0.0).rgb;\n var albedo = B.col6.rgb;\n var grad = vec2f(0.0); // the surface's height slope along u and v\n var ao = in.ao;\n var dapple = k.light.w;\n var sheen = vec2f(k.cloth.z, k.cloth.w);\n let dbg = u32(k.ring.y);\n\n if ((dbg & 2u) != 0u) {\n albedo = select(B.col0.rgb, B.col6.rgb, mat == 3 || mat == 4);\n } else if (mat == 3) {\n // a PAGE: ivory with its fibre and tooth, dots printed on it, its fore-edge corners rounded\n let w = in.size.x;\n let h = in.size.y;\n let pp = nb_paper(k, B.col6.rgb, q, dqdx, dqdy, page, paper_tex, paper_samp);\n albedo = pp.albedo;\n grad = pp.g;\n albedo = mix(albedo, k.ruleInk.rgb, nb_ruling(k, B, q, w, h, px) * k.ruleInk.a);\n // the pen's ink, laid into the paper before the light: it takes the gutter, the dapple and the night with it\n let layer = nb_ink_layer(in.book, page);\n if (layer >= 0) {\n let ink = textureSampleLevel(ink_tex, ink_samp, in.uv, layer, 0.0);\n albedo = mix(albedo, ink.rgb, ink.a);\n }\n // the gutter's occlusion, and the page's own edge a breath darker\n ao *= 1.0 - 0.3 * exp(-q.x / 4.5) - 0.12 * exp(-q.x / 18.0);\n dapple = k.light.z;\n sheen = vec2f(5.0, 0.02);\n } else if (mat == 0) {\n // the COVER: the cloth, the band, the design\n albedo = nb_cover_albedo(k, B, q, px);\n grad = nb_cover_g(k, B, q, px);\n if (B.foot.y > 0.5 && q.x > B.page.w) { sheen = vec2f(6.0, 0.02); }\n } else if (mat == 1) {\n let end = nb_endpaper(k, B, q, px, in.size.w > 0.5, dqdx, dqdy, paper_tex, paper_samp);\n albedo = end.albedo;\n ao *= end.ao;\n grad = nb_paper(k, B.col5.rgb, q, dqdx, dqdy, 77.0, paper_tex, paper_samp).g * 0.6;\n dapple = k.light.z;\n sheen = vec2f(5.0, 0.015);\n } else if (mat == 2) {\n albedo = nb_board(k, B, q, px);\n } else if (mat == 4) {\n albedo = nb_edge(k, B, q, in.size.z, in.size.w, px);\n sheen = vec2f(4.0, 0.0);\n dapple = k.light.z;\n } else if (mat == 6) {\n // the RIBBON: satin — a grosgrain of fine ribs along it, and a sheen that runs along its length (Kajiya–Kay on the weave's direction)\n albedo = B.col4.rgb * (0.9 + 0.1 * sin(in.uv.y * 44.0));\n grad = vec2f(0.0, cos(in.uv.y * 44.0) * 0.12);\n sheen = vec2f(0.0, 0.0);\n let Hs = normalize(B.lamp.xyz + V);\n let th = dot(Tu, Hs);\n let satin = pow(max(sqrt(max(1.0 - th * th, 0.0)), 0.0), 48.0) * 0.16;\n albedo += vec3f(satin);\n dapple = k.light.w;\n } else {\n // the SPINE: the band's cloth; seen from inside (the hollow of an open book), in shade\n albedo = B.col1.rgb * (1.0 + nb_cloth(q, k.cloth.x, px).shade * 0.16);\n if (inside) { albedo *= 0.5; ao *= 0.7; }\n }\n\n // the bumped normal: the height's slope tips the face\n let nb = normalize(n - Tu * grad.x - Tv * grad.y);\n let L = B.lamp.xyz;\n let lam = min(max(dot(nb, L), 0.0) / max(L.z, 1e-3), k.light.y);\n // the book shades itself only while something of it rises over the rest (a sheet in the air, the cover swinging)\n var vis = 1.0;\n if (B.foot.w > 0.5) { vis = nb_shadow(k, B, in.world, n, bn.x * 6.2831853, shadow_tex, shadow_cmp); }\n // paper sees more sky than cloth: the facing page and the desk bounce light onto it\n let sky = select(k.light.x, mix(k.light.x, 1.0, 0.22), mat == 3);\n var light = (sky + (1.0 - sky) * lam * vis) * ao;\n if (mat == 3) {\n // paper is translucent: a sheet with the lamp behind it glows with what comes through\n let back = max(-dot(n, L), 0.0) / max(L.z, 1e-3);\n light += 0.32 * (1.0 - k.light.x) * min(back, 1.2) * vis * ao;\n }\n var g0 = 1.0;\n if ((dbg & 4u) == 0u) { g0 = sample_gobo(u, gobo_tex, gobo_samp, nb_desk_at(u, in.world), bn.z); }\n let gobo = mix(1.0, g0, dapple);\n var c = nb_colour(u, albedo, light, gobo, bn.y);\n // the sheen: the lamp's glint on cloth and paper, where the lamp reaches\n let Hh = normalize(L + V);\n let sp = pow(max(dot(nb, Hh), 0.0), sheen.x) * sheen.y * vis * g0;\n c += vec3f(sp * (1.0 - u.night.x * 0.85));\n\n return vec4f(c, 1.0);\n}\n\n// ---- the mat under a book: its shadow, its contact, its ring\n\nstruct ROut {\n @builtin(position) clip: vec4f,\n @location(0) world: vec2f,\n @location(1) @interpolate(flat) book: u32,\n}\n\nconst RQUAD = array<vec2f, 6>(vec2f(0.0, 0.0), vec2f(1.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 0.0), vec2f(1.0, 1.0), vec2f(0.0, 1.0));\n\n@vertex\nfn vs_recv(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> ROut {\n let B = books[iid];\n var out: ROut;\n let p = mix(B.recv.xy, B.recv.zw, RQUAD[vid]);\n out.clip = nb_clip(k, vec3f(p, 0.0));\n out.world = p;\n out.book = iid;\n return out;\n}\n\n// The book's footprint on the desk, in its own coordinates: the back board, and — once the cover has come down — the spine and the front board beside it.\nfn nb_footprint(B: NbBook, p: vec2f) -> f32 {\n let w = B.size.x;\n let h = B.size.y;\n let r = B.size.w;\n let back = nb_sd_fore(p + vec2f(w * 0.5, h * 0.5), w, h, r);\n if (B.foot.x < 0.5) { return back; }\n let sw = B.open.z;\n let front = nb_sd_fore(vec2f(-w * 0.5 - sw - p.x, p.y + h * 0.5), w, h, r);\n let spine = nb_sd_box(p - vec2f(-w * 0.5 - sw * 0.5, 0.0), vec2f(sw * 0.5 + 0.5, h * 0.5), 0.0);\n return min(back, min(front, spine));\n}\n\n@fragment\nfn fs_recv(in: ROut) -> @location(0) vec4f {\n let B = books[in.book];\n let P = vec3f(in.world, 0.0);\n let bn = textureSampleLevel(noise_tex, noise_samp, (in.clip.xy + k.ring.zw) * u.noise.z + u.noise.xy, 0.0).rgb;\n let vis = nb_shadow(k, B, P, vec3f(0.0, 0.0, 1.0), bn.x * 6.2831853, shadow_tex, shadow_cmp);\n let aS = k.shadow.z * (1.0 - vis);\n // the contact: where the case meets the mat, fading as it rises\n let L = (B.inv * vec4f(P, 1.0)).xyz;\n let d = nb_footprint(B, L.xy);\n let rise = max(-L.z, 0.0);\n let touch = 1.0 - smoothstep(0.0, k.shadow2.y, rise);\n let s = k.shadow2.x;\n let aC = k.shadow.w * touch * exp(-max(d, 0.0) * max(d, 0.0) / (2.0 * s * s)) * step(-0.5, d);\n let a = 1.0 - (1.0 - aS) * (1.0 - aC);\n var c = vec4f(k.castCol.rgb * a, a);\n // the selection ring, on the mat around the book\n let css = 1.0 / max(k.eye.w, 1e-6);\n let rw = k.select.w * css;\n let rd = abs(d - k.ring.x * css) - rw * 0.5;\n let ring = nb_cov(rd, css / max(k.shadow2.w, 1.0)) * B.look.z;\n c = vec4f(k.select.rgb * ring, ring) + c * (1.0 - ring);\n return c;\n}\n";
14
+ readonly "paper/paper-card.wgsl": "// The note as the paper pass AND the flat-card pipeline draw it (K7b, design-016 §6 K7): the corner's clip position and\n// the fragment, over the note's bindings by their own names — `papers`, `paper_k`, `paper_ink` … — beside the slot's `u`,\n// `gobo_tex`, `noise_tex` and the pipeline's `LIT_ELSEWHERE`, which whichever entry composes them declares. One quad per\n// sheet, expanded by the shadow's reach and the AA; one `shade_paper` per covered fragment in world units, premultiplied,\n// through the presentation's opacity and the portal clip; alpha < 0 = discard.\n\nconst PAPER_CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0),\n);\n\nfn paper_quad(slot: u32, vid: u32) -> vec4f {\n let P = papers[slot];\n let zoom = mat_zoom(u);\n let px = 1.0 / (zoom * mat_dpr(u));\n // the tilted sheet's bounds, grown by everything the fragment can paint outside it: the shadow's reach and the AA\n let hmax = P.lift + P.curl * (1.0 + P.cornerCurl);\n let pad = length(P.slope) * hmax + 2.5 * (P.shadow.x + P.shadow.z * hmax) + 3.0 * px;\n let ext = vec2f(abs(P.rot.x) * P.half.x + abs(P.rot.y) * P.half.y, abs(P.rot.y) * P.half.x + abs(P.rot.x) * P.half.y) + vec2f(pad);\n let lo = (P.centre - ext - u.cam.xy) * zoom; // CSS px\n let hi = (P.centre + ext - u.cam.xy) * zoom;\n if (hi.x < u.box.x || hi.y < u.box.y || lo.x > u.box.x + u.box.z || lo.y > u.box.y + u.box.w) { return vec4f(2.0, 2.0, 2.0, 1.0); } // degenerate: a culled instance collapses\n let corner = PAPER_CORNERS[vid];\n let pos = mix(lo, hi, corner * 0.5 + 0.5);\n return vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n}\n\nfn paper_frag(slot: u32, clip: vec4f) -> vec4f {\n let P = papers[slot];\n let dpr = mat_dpr(u);\n let px = 1.0 / (mat_zoom(u) * dpr);\n let p = clip.xy * px + u.cam.xy; // device px → world\n let c = shade_paper(P, u, paper_k, p, px, px * dpr, clip.xy, gobo_tex, paper_gobo_samp, noise_tex, paper_noise_samp, paper_ink, paper_ink_samp, LIT_ELSEWHERE);\n if (c.a < 0.002) { return vec4f(-1.0); }\n return c * (u.view.w * portal_cover(clip.xy / dpr, u.portals, u.clips, dpr));\n}\n";
15
+ readonly "paper/paper-pass.wgsl": "// The paper pass — the notes, instanced: one quad per sheet, expanded on the\n// vertex side by the shadow's reach and the AA, one `shade_paper` per covered\n// fragment in world units, premultiplied output through the presentation's\n// opacity and the portal clip (like the frames). The quad and the fragment are\n// paper-card.wgsl's — the flat-card pipeline draws a note with the same two (K7b).\n\n// The slot is lit from elsewhere (MINIMAT.md §4): the pass's second pipeline — a pipeline constant, never a uniform flag.\noverride LIT_ELSEWHERE: bool = false;\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> paper_k: PaperUniforms;\n@group(0) @binding(2) var<storage, read> papers: array<Paper>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var paper_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var paper_noise_samp: sampler;\n@group(0) @binding(7) var paper_ink: texture_2d_array<f32>; // the ink pages: r8 coverage, one layer of rasters per binding\n@group(0) @binding(8) var paper_ink_samp: sampler;\n@group(0) @binding(9) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VSOut {\n var out: VSOut;\n out.idx = order[iid];\n out.clip = paper_quad(out.idx, vid);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let c = paper_frag(in.idx, in.clip);\n if (c.a < 0.0) { discard; }\n return c;\n}\n";
16
+ readonly "paper/paper.wgsl": "// The PAPER — a sticky note as a material under the mat's one light\n// (STICKY.md). A PURE module: the records, the two uniform blocks and the\n// textures arrive as parameters; it names no colour. The sheet is a rounded\n// box in its own tilted frame with a HEIGHT off the mat — flat under the\n// adhesive strip, rising as t² to the free edge, its corners most, the whole\n// sheet raised while held — and that height does three things: it tilts the\n// normal the lamp shades, it moves the point the gobo is read at, and it casts\n// the sheet's shadow along the lamp's ground slope, blurred by how high the\n// rim is there. The ink is a coverage raster laid into the paper's albedo\n// before the light, so the dapple crosses the writing as it crosses the mat.\n\nfn paper_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\nfn paper_line(d: f32, w: f32, px: f32) -> f32 { return paper_cov(d, px) - paper_cov(d + w, px); }\n\nfn paper_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage — the shadow, straight out of the field.\nfn paper_blur_cov(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * paper_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n\n// Premultiplied \"src over dst\".\nfn paper_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n\n// World → the sheet's own frame; a world direction → the same.\nfn paper_local(P: Paper, p: vec2f) -> vec2f {\n let d = p - P.centre;\n return vec2f(P.rot.x * d.x + P.rot.y * d.y, -P.rot.y * d.x + P.rot.x * d.y);\n}\nfn paper_dir(P: Paper, v: vec2f) -> vec2f { return vec2f(P.rot.x * v.x + P.rot.y * v.y, -P.rot.y * v.x + P.rot.x * v.y); }\n\n// The sheet's height off the mat at a local point (paper.ts `heightAt` is the same arithmetic).\nfn paper_height(P: Paper, q: vec2f) -> f32 {\n let hy = P.half.y;\n let flat = -hy + P.glue * 2.0 * hy;\n let t = saturate((q.y - flat) / max(2.0 * hy * (1.0 - P.glue), 1.0e-6));\n let c = pow(min(abs(q.x) / max(P.half.x, 1.0e-6), 1.0), 6.0);\n return P.lift + P.curl * t * t * (1.0 + P.cornerCurl * c);\n}\n\n// The sheet's normal from its height, by central differences one world unit apart — the slope\n// exaggerated by the relief (`shadow.w`), or a few px of curl over a hundred would shade nothing.\nfn paper_normal(P: Paper, q: vec2f) -> vec3f {\n let e = 1.0;\n let dx = paper_height(P, q + vec2f(e, 0.0)) - paper_height(P, q - vec2f(e, 0.0));\n let dy = paper_height(P, q + vec2f(0.0, e)) - paper_height(P, q - vec2f(0.0, e));\n return normalize(vec3f(-dx * P.shadow.w / (2.0 * e), -dy * P.shadow.w / (2.0 * e), 1.0));\n}\n\n// A world point raised `h` world units off the desk, in the projector's frame (mat.wgsl `desk_of` with a height).\nfn paper_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\n\n// The paper's colour on screen under the mat's light: by day the reference's grade on the\n// sheet's sRGB albedo — lit, the identity, so a configured byte is the drawn byte — or, at\n// `chain` 1, through the mat's own double gamma (what the sage goes through); by night the\n// eye's chain on the sheet's linear albedo, as the mat's. Between, the cross-fade.\nfn paper_colour(u: MatUniforms, albedo: vec3f, gobo: f32, noise: f32, chain: f32) -> vec3f {\n let day = shade_mat(u, mix(albedo, srgb_to_linear(albedo), chain), 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// K7b — the writing GREEKED at `nq` (note units from the sheet's top-left), as a mini mat's chip greeks a note (MINIMAT.md §5):\n// each line a stroke of the pen's ink from the text's left edge along its width, on its x-height's middle (0.32 em over the\n// baseline), `far.z` × the em thick — its coverage at `far.w`, the greek's presence (theme.ts `PAPER.far`).\nfn paper_greek(P: Paper, k: PaperUniforms, nq: vec2f, px: f32) -> f32 {\n let n = min(u32(P.greek.z), 6u);\n let em = P.greek.y;\n let r = 0.5 * k.far.z * em;\n var lines = P.glines;\n var cov = 0.0;\n for (var i = 0u; i < n; i++) {\n let v = lines[i / 2u];\n let L = select(v.xy, v.zw, (i & 1u) == 1u);\n let y = L.x - 0.32 * em;\n cov = max(cov, paper_cov(sdf_segment(nq, vec2f(P.greek.x, y), vec2f(P.greek.x + L.y, y)) - r, px));\n }\n return cov * k.far.w;\n}\n\n// One note at a world point `p`: premultiplied colour. `px` is world units per DEVICE px,\n// `css` world units per CSS px; `frag` the framebuffer pixel (the blue noise's key); `lit` the\n// slot is lit from elsewhere (a mini mat's inside, a handover — MINIMAT.md §4): a PIPELINE constant,\n// never a uniform flag, so a note under its own lamp compiles to exactly what it always did.\nfn shade_paper(P: Paper, u: MatUniforms, k: PaperUniforms, p: vec2f, px: f32, css: f32, frag: vec2f,\n gobo_tex: texture_2d<f32>, gobo_samp: sampler, noise_tex: texture_2d<f32>, noise_samp: sampler,\n ink_tex: texture_2d_array<f32>, ink_samp: sampler, lit: bool) -> vec4f {\n let q = paper_local(P, p);\n let d = sdf_round_box(q, P.half, P.radius);\n // The shadow, outside the sheet only: the silhouette cast along the lamp's ground slope\n // by the height of the nearest rim point, blurred by that height — a flat strip lies in\n // a tight contact shadow, the curled edge and a held sheet in a wider, softer one.\n let rim = clamp(q, -P.half, P.half);\n let hr = paper_height(P, rim);\n let ds = sdf_round_box(q - paper_dir(P, P.slope) * hr, P.half, P.radius);\n let sh = paper_blur_cov(ds, P.shadow.x + P.shadow.z * hr) * P.shadow.y * (1.0 - paper_cov(d, px));\n var acc = vec4f(0.0, 0.0, 0.0, sh);\n if (d > px) { return acc * P.alpha; }\n let cov = paper_cov(d, px);\n\n // K7b — THE FAR LOD (theme.ts `PAPER.far`): the sheet's longer side on screen, CSS px. Under the band's high edge the writing\n // crossfades to its greeked lines and the FIBRE — the finest detail, a grain that only aliases once a texel spans units — fades\n // out; under its low edge the lines alone and no fibre is computed. The lamp's shading of the curl stays (it reads at any size).\n // Above the band `fine` is 1: the full path, the very arithmetic it always was.\n let size = 2.0 * max(P.half.x, P.half.y) / css;\n let far = 1.0 - smoothstep(k.far.x, k.far.y, size); // edges ascending: a descending smoothstep is undefined in Metal (two Dawns differ)\n let fine = 1.0 - far;\n\n // The sheet: its height, its normal, the lamp — the flat strip reads 1, the curl turns toward or from the light.\n let h = paper_height(P, q);\n let n = paper_normal(P, q);\n let L = vec3f(paper_dir(P, P.lamp.xy), P.lamp.z);\n let diffuse = saturate(dot(n, L)) / max(L.z, 1.0e-3);\n\n // The albedo: the paper with its fibre (in the sheet's own units, so the grain sticks to it), the ink laid into it.\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n var fibre = 0.0;\n if (fine > 0.0) {\n fibre = ((value_noise(q * 0.9) - 0.5) + (value_noise(q * 3.1) - 0.5) * 0.5 + (bn.x - 0.5) * 0.5) * P.paper.w;\n if (fine < 1.0) { fibre = fibre * fine; }\n }\n var albedo = P.paper.xyz + vec3f(fibre * 1.1, fibre, fibre * 0.7);\n let nq = q + P.half; // note units from the sheet's top-left\n let uv = P.uv.xy + (nq / (2.0 * P.half)) * (P.uv.zw - P.uv.xy);\n var ink = 0.0;\n if (P.layer >= 0) { ink = textureSampleLevel(ink_tex, ink_samp, uv, P.layer, 0.0).r; }\n // The wipe: the pen still writing the newest glyph — inside its box the ink appears left to right.\n if (P.wipe.z > P.wipe.x && nq.x >= P.wipe.x && nq.x <= P.wipe.z && nq.y >= P.wipe.y && nq.y <= P.wipe.w) {\n let edge = P.wipe.x + P.marks.x * (P.wipe.z - P.wipe.x + k.knobs.y);\n ink *= 1.0 - smoothstep(edge - k.knobs.y, edge, nq.x);\n }\n // K7b: the greeked lines in the raster's place — crossfaded within the band, alone under it (where no raster is held).\n // K9 R6: and held at FULL while the sheet holds no raster at any size (`layer < 0` — its ink is on the frame queue's way, or\n // was let go at far and not yet laid on the way back): the greek stands in for the writing until the raster lands, never a\n // blank sheet. The fibre keeps the band's word (`fine`): the paper's grain is the size's, not the raster's.\n var greeked = far;\n if (P.layer < 0 && P.greek.z > 0.5) { greeked = 1.0; }\n if (greeked > 0.0 && P.greek.z > 0.5) { ink = mix(ink, paper_greek(P, k, nq, px), greeked); }\n albedo = mix(albedo, P.ink.xyz, ink * P.ink.w);\n // the lamp's shading, in the display's own gamma (the mat's law: a linear multiply shows as shade^(1/2.2))\n albedo = albedo * pow(max(diffuse, 0.0), 1.0 / 2.2);\n // the dapple: under the slot's own lamp the desk point itself — else where the LIGHT sees it: the lamp of the desk a mini mat lies on (MINIMAT.md §4)\n var gobo = sample_gobo(u, gobo_tex, gobo_samp, paper_desk_at(u, p, h), bn.z);\n if (lit) { gobo = lit_gobo(u, gobo_tex, gobo_samp, p, h, paper_desk_at(u, p, h), bn.z); }\n let colour = paper_colour(u, clamp(albedo, vec3f(0.0), vec3f(1.0)), gobo, bn.y, k.knobs.x);\n acc = paper_over(vec4f(colour * cov, cov), acc);\n\n // The caret: a bar of the ink at the writing head, on the host's blink.\n if (P.marks.y > 0.5) {\n let cw = k.knobs.z * css;\n let cq = nq - vec2f(P.caret.x, P.caret.y - P.caret.z);\n let ch = P.caret.z + P.caret.w;\n let cd = sdf_box(cq - vec2f(cw * 0.5, ch * 0.5), vec2f(cw * 0.5, ch * 0.5));\n let cc = paper_cov(cd, px) * 0.9;\n acc = paper_over(vec4f(P.ink.xyz * cc, cc), acc);\n }\n // The sole-selection ring, inside the edge, in the theme's select.\n let ring = paper_line(d, k.knobs.w * css, px) * P.ring;\n acc = paper_over(vec4f(k.select.xyz * ring, ring), acc);\n return acc * P.alpha;\n}\n";
17
+ readonly "photo/photo-card.wgsl": "// The print as the photo pass AND the flat-card pipeline draw it (K7b, design-016 §6 K7): the corner's clip position, the\n// fragment and the THUMBNAIL's texel, over the print's bindings by their own names — `photos`, `photo_k`, `photo_thumbs` … —\n// beside the slot's `u`, `gobo_tex`, `noise_tex` and the pipeline's `LIT_ELSEWHERE`, which whichever entry composes them\n// declares. `shade_photo` reads a picture through `photo_texel`: the pass's entry reads the detail pool there, else the\n// thumbnail; the card binds no pool and reads the thumbnail alone (a print with a detail bound is the pass's to draw).\n\n// The thumbnail array's layer side (pictures.ts `THUMB`).\nconst PHOTO_THUMB = 512.0;\n\nconst PHOTO_CORNERS = array<vec2f, 6>(\n vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0),\n vec2f(-1.0, -1.0), vec2f(1.0, 1.0), vec2f(-1.0, 1.0),\n);\n\n// A picture's texel at (uv, lod) from its THUMBNAIL — lod in its whole chain's levels: the layer (its `tier.x`), which starts at\n// the chain's level k (`tier.y`) — the picture at its origin, its edge carried to the layer's — at lod − k (a print larger than\n// the layer serves, before its detail comes, magnifies the layer's first level).\nfn photo_thumb(P: Photo, uv: vec2f, lod: f32) -> vec4f {\n let k = P.tier.y;\n let size = max(floor(P.image.xy / exp2(k)), vec2f(1.0)); // the layer's picture at its level 0, texels\n return textureSampleLevel(photo_thumbs, photo_pic_samp, uv * size / PHOTO_THUMB, i32(P.tier.x), max(lod - k, 0.0));\n}\n\nfn photo_quad(slot: u32, vid: u32) -> vec4f {\n let P = photos[slot];\n let zoom = mat_zoom(u);\n let lo = (P.bounds.xy - u.cam.xy) * zoom; // CSS px\n let hi = (P.bounds.zw - u.cam.xy) * zoom;\n if (P.centre.w <= 0.0 || hi.x < 0.0 || hi.y < 0.0 || lo.x > u.view.x || lo.y > u.view.y) { return vec4f(2.0, 2.0, 2.0, 1.0); } // degenerate: a culled instance collapses\n let pos = mix(lo, hi, PHOTO_CORNERS[vid] * 0.5 + 0.5);\n return vec4f(pos.x / u.view.x * 2.0 - 1.0, 1.0 - pos.y / u.view.y * 2.0, 0.0, 1.0);\n}\n\nfn photo_frag(slot: u32, clip: vec4f) -> vec4f {\n let P = photos[slot];\n let dpr = mat_dpr(u);\n let px = 1.0 / (mat_zoom(u) * dpr);\n let s = clip.xy * px + u.cam.xy; // device px → world\n let c = shade_photo(P, u, photo_k, s, px, clip.xy, gobo_tex, photo_gobo_samp, noise_tex, photo_noise_samp, LIT_ELSEWHERE);\n if (c.a < 0.002) { return vec4f(-1.0); }\n return c * (u.view.w * portal_cover(clip.xy / dpr, u.portals, u.clips, dpr));\n}\n";
18
+ readonly "photo/photo-pass.wgsl": "// The photo pass — one quad per print over the world rect `resolvePhoto`\n// measured (the sheet as seen and its shadow), one `shade_photo` per covered\n// fragment, premultiplied output through the presentation's opacity and the\n// portal clip. The pictures are SHARED (group 1 — K6a, K-L4): a print's record\n// names its thumbnail's layer in one array and, while it is large on screen, the\n// pool slot of its detail — so a run of prints is ONE instanced draw; the paint\n// order is the instance order. The quad, the fragment and the thumbnail's texel are\n// photo-card.wgsl's — the flat-card pipeline draws a print with the same three (K7b).\n\n// The slot is lit from elsewhere (MINIMAT.md §4): the pass's second pipeline — a pipeline constant, never a uniform flag.\noverride LIT_ELSEWHERE: bool = false;\n\n@group(0) @binding(0) var<uniform> u: MatUniforms;\n@group(0) @binding(1) var<uniform> photo_k: PhotoUniforms;\n@group(0) @binding(2) var<storage, read> photos: array<Photo>;\n@group(0) @binding(3) var gobo_tex: texture_2d<f32>; // the mat's animated silhouette (its wind target)\n@group(0) @binding(4) var photo_gobo_samp: sampler;\n@group(0) @binding(5) var noise_tex: texture_2d<f32>;\n@group(0) @binding(6) var photo_noise_samp: sampler;\n@group(0) @binding(7) var photo_pic_samp: sampler; // trilinear, clamped\n@group(0) @binding(8) var<storage, read> order: array<u32>; // the draw list: paint index → the record's slot (persistent records, D6)\n@group(1) @binding(0) var photo_thumbs: texture_2d_array<f32>; // every picture's chain from its first level ≤ THUMB² (rgba8unorm-srgb), a layer each\n@group(1) @binding(1) var detail0: texture_2d<f32>; // the pool: a large print's chain from the finest level it samples\n@group(1) @binding(2) var detail1: texture_2d<f32>;\n@group(1) @binding(3) var detail2: texture_2d<f32>;\n@group(1) @binding(4) var detail3: texture_2d<f32>;\n@group(1) @binding(5) var detail4: texture_2d<f32>;\n@group(1) @binding(6) var detail5: texture_2d<f32>;\n@group(1) @binding(7) var detail6: texture_2d<f32>;\n@group(1) @binding(8) var detail7: texture_2d<f32>;\n\n// A picture's texel at (uv, lod) — lod in its WHOLE chain's levels. `tier`: its thumbnail's layer, the chain level that layer\n// starts at (k), the pool slot of its detail (−1: none) and the level the detail starts at (j). Both are cuts of one chain: the\n// detail at lod − j, else the layer (photo-card.wgsl `photo_thumb`).\nfn photo_texel(P: Photo, uv: vec2f, lod: f32) -> vec4f {\n let slot = i32(P.tier.z);\n let l = max(lod - P.tier.w, 0.0);\n switch slot {\n case 0: { return textureSampleLevel(detail0, photo_pic_samp, uv, l); }\n case 1: { return textureSampleLevel(detail1, photo_pic_samp, uv, l); }\n case 2: { return textureSampleLevel(detail2, photo_pic_samp, uv, l); }\n case 3: { return textureSampleLevel(detail3, photo_pic_samp, uv, l); }\n case 4: { return textureSampleLevel(detail4, photo_pic_samp, uv, l); }\n case 5: { return textureSampleLevel(detail5, photo_pic_samp, uv, l); }\n case 6: { return textureSampleLevel(detail6, photo_pic_samp, uv, l); }\n case 7: { return textureSampleLevel(detail7, photo_pic_samp, uv, l); }\n default: {}\n }\n return photo_thumb(P, uv, lod);\n}\n\nstruct VSOut {\n @builtin(position) clip: vec4f,\n @location(0) @interpolate(flat) idx: u32,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VSOut {\n var out: VSOut;\n out.idx = order[iid];\n out.clip = photo_quad(out.idx, vid);\n return out;\n}\n\n@fragment\nfn fs(in: VSOut) -> @location(0) vec4f {\n let c = photo_frag(in.idx, in.clip);\n if (c.a < 0.0) { discard; }\n return c;\n}\n";
19
+ readonly "photo/photo.wgsl": "// The PHOTO — a glossy print as a material under the desk's one light\n// (PHOTO.md). A PURE module: the record, the two uniform blocks and the\n// textures arrive as parameters. The sheet is a rounded box in 3D — a centre\n// with a height, its own axes on a tilted plane, a bend — seen through a\n// LOCAL eye over its anchor (photo.ts `unproject` is this arithmetic on the\n// CPU): the fragment's desk point is a ray from that eye, cast onto the sheet.\n// Its shadow is the same cast from the desk point toward the light: where the\n// ray meets the sheet, how high the sheet is there sets the blur. The picture\n// is laid into the paper's albedo before the light (so the dapple crosses it\n// as it crosses the mat); the coating's gloss is added after it — the lamp's\n// highlight where the sheet turns toward it, and the room's sheen at a slant.\n\nfn photo_cov(d: f32, px: f32) -> f32 { return clamp(0.5 - d / px, 0.0, 1.0); }\n\nfn photo_erf(x: f32) -> f32 {\n let s = sign(x);\n let a = abs(x);\n let t = 1.0 / (1.0 + 0.3275911 * a);\n let y = 1.0 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * exp(-a * a);\n return s * y;\n}\n// Gaussian-blurred coverage of the silhouette — the shadow, straight out of the field.\nfn photo_blur_cov(d: f32, sigma: f32) -> f32 { return 0.5 - 0.5 * photo_erf(d / max(sigma * 1.4142136, 1.0e-4)); }\n\nfn photo_over(src: vec4f, dst: vec4f) -> vec4f { return src + dst * (1.0 - src.a); }\n\nfn photo_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\n// The bend's offset along the normal at a point of the sheet (photo.ts `bendAt`):\n// the rest curl rises toward the short edges; a held sheet droops away from the grab point.\nfn photo_bend(P: Photo, q: vec2f) -> f32 {\n let d = length(q - P.bend.zw) / (2.0 * length(vec2f(P.ex.w, P.ey.w)));\n let cu = q.x / max(P.ex.w, P.ey.w);\n return P.bend.y * cu * cu - P.bend.x * d * d;\n}\n\n// The sheet's normal with its bend, by central differences one world unit apart.\nfn photo_normal(P: Photo, q: vec2f) -> vec3f {\n let dx = photo_bend(P, q + vec2f(1.0, 0.0)) - photo_bend(P, q - vec2f(1.0, 0.0));\n let dy = photo_bend(P, q + vec2f(0.0, 1.0)) - photo_bend(P, q - vec2f(0.0, 1.0));\n return normalize(P.n.xyz - P.ex.xyz * (dx * 0.5) - P.ey.xyz * (dy * 0.5));\n}\n\n// A ray (origin o, direction D) onto the sheet: the point in 3D and the sheet's frame, with one bend correction.\nstruct PhotoHit { p: vec3f, q: vec2f, ok: bool }\nfn photo_cast(P: Photo, o: vec3f, D: vec3f) -> PhotoHit {\n var h: PhotoHit;\n let n = P.n.xyz;\n let dn = dot(n, D);\n if (abs(dn) < 1.0e-5) { h.ok = false; return h; }\n var t = dot(n, P.centre.xyz - o) / dn;\n var p = o + D * t;\n var q = vec2f(dot(p - P.centre.xyz, P.ex.xyz), dot(p - P.centre.xyz, P.ey.xyz));\n t = dot(n, P.centre.xyz + n * photo_bend(P, q) - o) / dn;\n p = o + D * t;\n let r = p - (P.centre.xyz + n * photo_bend(P, q));\n h.p = p;\n h.q = vec2f(dot(r, P.ex.xyz), dot(r, P.ey.xyz));\n h.ok = t > 0.0;\n return h;\n}\n\n// The picture at a point of the sheet: sRGB-encoded rgb, and whether this point is the picture (1) or the border (0). Its texel\n// at (uv, lod) is the entry's `photo_texel` (K6a: the entry binds where the pixels live — a shared array's layer, a pool's detail).\nfn photo_picture(P: Photo, q: vec2f, px: f32) -> vec4f {\n let inner = vec2f(P.ex.w, P.ey.w) - vec2f(P.eye.w);\n if (P.image.w < 0.5 || inner.x <= 0.0 || inner.y <= 0.0) { return vec4f(0.0); }\n let d = sdf_box(q, inner);\n let cov = photo_cov(d, px);\n if (cov <= 0.0) { return vec4f(0.0); }\n let uv = clamp((q + inner) / (2.0 * inner), vec2f(0.0), vec2f(1.0));\n // the mip: texels per device pixel along the picture's wider axis (the ray's own foreshortening is small; the eye is far)\n let lod = clamp(log2(max(P.image.x / (2.0 * inner.x), P.image.y / (2.0 * inner.y)) * px), 0.0, P.image.z - 1.0);\n let c = photo_texel(P, uv, lod);\n return vec4f(photo_encode(c.rgb), cov * c.a);\n}\n\n// The print by day: the mat's light — the reference's grade on the sRGB albedo, lit the identity (a\n// picture's byte is the drawn byte), the palm's shade darkening it as it darkens the mat.\n//\n// The print by NIGHT (PHOTO.md §4a): the same Moon, the same palm, seen by the eye that sees by moonlight\n// — but the print keeps its colour where it is a PICTURE. The fovea has no rods: what the eye looks at it\n// sees with cones alone, white-balanced to the thing it attends to (CIECAM's D → 1), so a picture keeps its\n// hue while its light is the Moon's. The paper around it is seen as the mat is (night_mat's model: the\n// Moon's partly adapted warmth, the rods' blue by the mesopic weight at its own luminance, their noise).\n// The level is the mat's night LAW, not its exposure number: moonlit = `k.night.x` (½) of the day's\n// luminance (MAT.md §2; the mat's exposure is fitted to its double-gamma day look, a print's day look is\n// its plain sRGB). The shade is the night's: the sky's fill `moon.w` where the palm blocks the Moon.\n// `keep` = how far a picture's pixel is seen foveally (1 = its hue exactly).\nfn photo_night(u: MatUniforms, k: PhotoUniforms, albedo: vec3f, gobo: f32, pic: f32, noise: f32) -> vec3f {\n let lin = srgb_to_linear(albedo);\n let shade = u.moon.w + (1.0 - u.moon.w) * gobo;\n let fovea = pic * k.night.y;\n // the cones: the paper takes the Moon's partly adapted warmth, the picture the eye's own white\n let cone = lin * mix(u.moon.xyz, vec3f(1.0), fovea) * shade * k.night.x;\n // the rods, as night_mat has them: V' normalised to white, drawn in the rods' blue\n let rod = max(night_scotopic(cone), 0.0) / night_scotopic(vec3f(1.0));\n let L = u.night.y * night_luminance(lin * u.moon.xyz * shade) * 0.3183098862; // cd/m² on the print: E · ρ / π\n let m = mix(night_mesopic(L), 1.0, fovea);\n let see = mix(rod * u.rod.xyz, cone, m);\n // the dark is not black (MAT.md §1.5): the paper wears Eigengrau and the rods' snow as the mat does; the fovea's\n // picture takes the same floor as a NEUTRAL grey of Eigengrau's luminance — a grey keeps the hue, the blue would not\n let floor = mix(u.eigengrau.xyz, vec3f(night_luminance(u.eigengrau.xyz)), fovea);\n return night_encode(floor + see) + (noise - 0.5) * u.night.w * (1.0 - fovea);\n}\n\nfn photo_colour(u: MatUniforms, k: PhotoUniforms, albedo: vec3f, gobo: f32, pic: f32, noise: f32) -> vec3f {\n let day = shade_mat(u, albedo, gobo);\n if (u.night.x <= 0.0) { return day; }\n let night = photo_night(u, k, albedo, gobo, pic, noise);\n if (u.night.x >= 1.0) { return night; }\n return mix(day, night, u.night.x);\n}\n\n// A world point raised `h` world units off the desk, in the projector's frame (mat.wgsl `desk_of` with a height).\nfn photo_desk_at(u: MatUniforms, world: vec2f, h: f32) -> vec3f {\n return vec3f(u.plane.x + world.x * u.plane.z, u.plane.w + h * u.plane.z, u.plane.y + world.y * u.plane.z);\n}\n\n// One print at a desk point `s`: premultiplied colour. `px` is world units per DEVICE px; `frag` the framebuffer pixel; `lit`\n// the slot is lit from elsewhere (a mini mat's inside, a handover — MINIMAT.md §4): a PIPELINE constant, never a uniform flag,\n// so a print under its own lamp compiles to exactly what it always did (the note's precedent, paper.wgsl `shade_paper`).\nfn shade_photo(P: Photo, u: MatUniforms, k: PhotoUniforms, s: vec2f, px: f32, frag: vec2f,\n gobo_tex: texture_2d<f32>, gobo_samp: sampler, noise_tex: texture_2d<f32>, noise_samp: sampler, lit: bool) -> vec4f {\n let half = vec2f(P.ex.w, P.ey.w);\n let L = P.light.xyz;\n\n // THE SHADOW: from the desk point toward the light, onto the sheet. Its height there is the ray's\n // own parameter (the ray rises one unit per unit), so a flat sheet lies in its contact shadow and a\n // lifted or tilted one casts a wider, softer, offset one — the tilted outline, not a rect's.\n var sh = 0.0;\n let a = vec3f(-P.slope.xy, 1.0);\n let hs = photo_cast(P, vec3f(s, 0.0), a);\n if (hs.ok || P.centre.z < 0.5) {\n let height = max(hs.p.z, 0.0);\n let ds = sdf_round_box(hs.q, half, P.n.w);\n let pen = photo_blur_cov(ds, k.penumbra.y + k.penumbra.z * height) * P.light.w;\n let con = photo_blur_cov(ds, k.contact.y) * k.contact.x * (1.0 - smoothstep(0.0, k.contact.z, height));\n sh = 1.0 - (1.0 - pen) * (1.0 - con);\n }\n var acc = vec4f(k.shade.xyz * sh, sh);\n\n // THE SHEET: the ray from the local eye through the desk point.\n let eye = P.eye.xyz;\n let hit = photo_cast(P, eye, vec3f(s, 0.0) - eye);\n if (!hit.ok) { return acc * P.centre.w; }\n let q = hit.q;\n // a device pixel on the desk is (eye − h)/eye of that on the sheet\n let pxs = px * max(eye.z - hit.p.z, 1.0) / eye.z;\n let d = sdf_round_box(q, half, P.n.w);\n if (d > pxs) { return acc * P.centre.w; }\n let cov = photo_cov(d, pxs);\n\n let bn = textureSampleLevel(noise_tex, noise_samp, frag * u.noise.z + u.noise.xy, 0.0).rgb;\n // the paper: resin-coated, a faint fibre that sticks to the sheet\n let fibre = ((value_noise(q * 0.7) - 0.5) + (bn.x - 0.5) * 0.5) * k.paper.w;\n var albedo = k.paper.xyz + vec3f(fibre);\n let pic = photo_picture(P, q, pxs);\n albedo = mix(albedo, pic.rgb, pic.a);\n\n // the light: the lamp's diffuse on the bent sheet (flat = 1, the configured byte), in the display's gamma as the mat's law\n let n = photo_normal(P, q);\n let diffuse = clamp(dot(n, L) / max(L.z, 1.0e-3), 0.0, 1.6);\n albedo = clamp(albedo * pow(diffuse, 1.0 / 2.2), vec3f(0.0), vec3f(1.0));\n // the dapple: under the slot's own lamp the sheet's point itself — else where the LIGHT sees it: the lamp of the desk a mini mat lies on (MINIMAT.md §4)\n var gobo = sample_gobo(u, gobo_tex, gobo_samp, photo_desk_at(u, hit.p.xy, hit.p.z), bn.z);\n if (lit) { gobo = lit_gobo(u, gobo_tex, gobo_samp, hit.p.xy, hit.p.z, photo_desk_at(u, hit.p.xy, hit.p.z), bn.z); }\n var colour = photo_colour(u, k, albedo, gobo, pic.a, bn.y);\n // the light on the coating: the Sun's, or by night the Moon's — the same palm blocks both, and the Moon's\n // glint is dimmer (the night's level) and cooler (what the rods make of a white highlight)\n let night = saturate(u.night.x);\n let glint = mix(vec3f(1.0, 0.985, 0.95), vec3f(0.78, 0.86, 1.0) * k.night.x, night);\n\n // the coating: the lamp's highlight (blocked where the palm shades it) and the room's sheen at a slant (Schlick)\n let V = normalize(eye - hit.p);\n let H = normalize(L + V);\n let spec = pow(max(dot(n, H), 0.0), k.gloss.y) * k.gloss.x * gobo;\n let R = reflect(-V, n);\n let window = smoothstep(0.62, 0.97, dot(R, L));\n let fres = 0.04 + 0.96 * pow(1.0 - max(dot(n, V), 0.0), 5.0);\n let sheen = (0.12 + 0.88 * window) * fres * k.gloss.z * (0.4 + 0.6 * gobo);\n colour = colour + glint * (spec + sheen);\n // the cut edge: a hairline a shade darker, where the light grazes the paper's thickness\n let edge = photo_cov(d + 0.9 * pxs, pxs) ;\n colour = colour * (1.0 - 0.18 * (1.0 - edge));\n colour = clamp(colour + (bn.y - 0.5) / 255.0, vec3f(0.0), vec3f(1.0));\n\n acc = photo_over(vec4f(colour * cov, cov), acc);\n return acc * P.centre.w;\n}\n";
20
+ };
21
+ export type WgslFile = keyof typeof WGSL;
@@ -0,0 +1,12 @@
1
+ /** Stable React projections over the headless Canvas SDK facade. */
2
+ import type { CanvasCatalogSection, CanvasDiagnostic, CanvasSessionValue, Entity, FramePreviewSnapshot, Tool } from "../../core/src/index";
3
+ export declare function useCurrentCanvas(): CanvasSessionValue;
4
+ export declare function useCanvasCatalog(): readonly CanvasCatalogSection[];
5
+ export declare function useCanvasTools(): readonly Tool[];
6
+ export declare function useCanvasDiagnostics(): readonly CanvasDiagnostic[];
7
+ /**
8
+ * Demand-driven semantic frame preview. Merely reading the hook does not scan
9
+ * the frame: useSyncExternalStore activates the core projection on subscribe
10
+ * and removes every observer on unmount.
11
+ */
12
+ export declare function useFramePreview(frame: Entity): FramePreviewSnapshot;