@descix/frqtl-node 0.6.1-p3.1 → 0.6.1-p3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CLAUDE.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A discrete cellular-automata physics engine (compiled Rust → WASM) for node: the same modules as
4
4
  `@descix/frqtl-web` (one source, built twice), plus the node engine door `bootNode` and the hosted eyes.
5
- `0.6.1-p3.1` (npm dist-tag `next`). Node ≥ 22.12.
5
+ `0.6.1-p3.3` (npm dist-tag `next`). Node ≥ 22.12.
6
6
 
7
7
  ## Install
8
8
  npm install @descix/frqtl-node
package/README.md CHANGED
@@ -4,7 +4,7 @@ FRQTL for node: a discrete cellular-automata physics engine (compiled Rust → W
4
4
  local per-pixel rules — a frame moves one pixel per tick, single occupancy, momentum — rather than force
5
5
  equations. Interference, blackbody spectra and atom-like binding emerge from the automaton.
6
6
 
7
- `0.6.1-p3.1` (npm dist-tag `next`). Node ≥ 22.12.
7
+ `0.6.1-p3.3` (npm dist-tag `next`). Node ≥ 22.12.
8
8
 
9
9
  ## Install
10
10
 
@@ -31,7 +31,7 @@ const tick = u.doTick(); // TickData: fr
31
31
  | `.` | the parts (`Wall`, `SphereEmitter`, `Tunnel`, `FrqtlVision`, …), `bootNode`, the engine accessors (`engine`, `engineState`), the capture contracts (`FrqtlCapture`, `RenderStages`, `FieldBuilder`, `CaptureEnvelope`) and `validateRendererProvider` |
32
32
  | `./eyes` | `openHostedEyes` — a `composite` still through the REAL renderer, in a managed headless browser (pulls the pinned `playwright`) |
33
33
  | `./parts`, `./parts/*` | the parts barrel; one part = one file |
34
- | `./project` | `FRQTL_WEB_SPECIFIERS`, `libPathOf`, `importMapFor`, `pageImportMapFor`, `FRQTL_WEB_HOST_SPECIFIERS`, `linkProject`, `sketchProject`, `SKETCH_ENTRY`, `projectIdentity`, `MANIFEST_FILE`, `MANIFEST_KEYS`, `normalizeManifest`, `parseManifest`, `serializeManifest`, `manifestFor`, `RECORD_FILES`, `RECORD_KEYS`, `CHANGE_KEYS`, `OPS`, `BY`, `CLIP_SECONDS`, `CLIP_NAME`, `clipName`, `jsonText`, `recordFolderFaults`, `applyRows` |
34
+ | `./project` | `FRQTL_WEB_SPECIFIERS`, `libPathOf`, `importMapFor`, `pageImportMapFor`, `FRQTL_WEB_HOST_SPECIFIERS`, `linkProject`, `sketchProject`, `SKETCH_ENTRY`, `projectIdentity`, `MANIFEST_FILE`, `MANIFEST_KEYS`, `normalizeManifest`, `parseManifest`, `serializeManifest`, `manifestFor`, `RECORD_FILES`, `RECORD_KEYS`, `CHANGE_KEYS`, `OPS`, `BY`, `CLIP_SECONDS`, `CLIP_NAME`, `clipName`, `jsonText`, `rowFaults`, `recordFolderFaults`, `applyRows` (stands alone: no other import is needed) |
35
35
  | `./registry` | the walk-through rungs, the showcase roster, the scene list (`sceneGroups`), `projectOf`, a showcase tile's link (`showcaseHref(row)` relative to the serving site, `showcaseUrl(row)` absolute on `SHOWCASE_SITE.origin`; `row` is an element of the same imported `EXHIBITS`, and the link carries the row's effective mode on a multi-mode rung), and how STUDIO resolved the link it opened with (`STUDIO_LANDING_ATTR` on `<html>`, a `STUDIO_LANDING_OUTCOMES` id) |
36
36
  | `./kinds` | `registerKind`, `kindNamed`, `kindOf`, `registeredKinds` |
37
37
  | `./host-view` | `declareHostView`, `HOST_VIEW_OUTCOMES`, `CONTRACT_VIOLATIONS`, `HOST_SDK_SCRIPT` — the DeSciX host-view declarer (pure: a node test can drive a call site with a stub SDK) |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@descix/frqtl-node",
3
- "version": "0.6.1-p3.1",
3
+ "version": "0.6.1-p3.3",
4
4
  "files": {
5
5
  "eyes/index.js": "b3563421030bb7fdd277eb697959fbe9107463cbb5478160edc7d887daa67be3",
6
6
  "fat/pkg-node/egpt_wasm.js": "5e6af1f6725c93c0e9ad7386f1785cbe639ce596a4caa19ca85729c1f2bea04d",
@@ -26,21 +26,21 @@
26
26
  "frqtl/compat/renderer/display-slot.js": "8a2e99836dcc2f95155f2cd8ffeb9267054e3848c2e8f2ce320ebaca22f80a9e",
27
27
  "frqtl/compat/renderer/lo-drawn-center.js": "5d285c8e6b0775db336de559855d28d752aed7544610f0430d8a389c530673cc",
28
28
  "frqtl/compat/renderer/orbit-bag.js": "b6671d3b35fcf5f79e228934a8032e729309e62b66920602f34c834f3f911681",
29
- "frqtl/compat/renderer/providers/godrays-layer.js": "be76fb8e69c74dd9bd0fb9aa6189a816d0ed29b9f4e04b60277be375b93d606c",
29
+ "frqtl/compat/renderer/providers/godrays-layer.js": "fdfde9469cdeefce87785a648b77ed46ead22de46369b3b70212446ef3cbb850",
30
30
  "frqtl/compat/renderer/providers/index.js": "fa669158a082a0081125c81694ca79962639caf86a575ee198a815f3e13a19e7",
31
- "frqtl/compat/renderer/providers/p5-provider.js": "39aa68c2232b123ed52ca235f264cfb57993f46a41d4b0ed8139bffbac2f64ac",
31
+ "frqtl/compat/renderer/providers/p5-provider.js": "d247d81c785b1d45b863a9e4081b2b85302106ddc0de75aece0f9a9553dabbc9",
32
32
  "frqtl/compat/renderer/providers/three-camera-contract.js": "644c1cc5e381cd2af897e69762216f84716808eeb5948efb989f48c47696bc60",
33
33
  "frqtl/compat/renderer/providers/three-camera-geometry.js": "a741f72c34005fc47fb4d53cc7ef2d2df4bfa7e8eecc9a0252b1083b3d5e301a",
34
- "frqtl/compat/renderer/providers/three-provider.js": "08410c488dcf3290d0a950331f33d8ba6882a391308d4d4598d9cb9aa96a4c9a",
34
+ "frqtl/compat/renderer/providers/three-provider.js": "30fc893f303144cd6fff66092c2e42e9880f7dc2da4fe6a27878d9b5b0c2e8dc",
35
35
  "frqtl/compat/renderer/renderCapabilities-common.js": "4e4bf31a8ab5b4b938fdbd517e30d28e9c8e90251b6fe1dbaafd575bf2d4ccf3",
36
36
  "frqtl/compat/renderer/renderer-provider-contract.js": "295574c6b10ce4c335b7c9a83941f3c1723b412c21f0004501add45e533bdb5a",
37
37
  "frqtl/compat/renderer/unprojectStill.js": "43c4286c124eaa238d963fe3ae5551a1c87727809d5245a129f368a117863c45",
38
38
  "frqtl/compat/vendor.js": "2393ada71cdbe750d409174e1ba9b098148796efdff746b315d706a0b32e58e8",
39
39
  "frqtl/experiments/_shared/boot-failure.js": "1a92e85b06acdefede5613f2fac7cd4e61d2aba590820f1bfa92f6829fcbbdd4",
40
40
  "frqtl/node-entry.js": "a401039b4fcf53ac620edbd80985aa4448122f2e3caaeaf4d5595f5e9d512b60",
41
- "frqtl/objects/FrqtlApp.js": "e3232b09eb576427df2a52393a5603b4c084d6edba5667b03afb161fd6d76b9b",
41
+ "frqtl/objects/FrqtlApp.js": "bc4b4b2206588cd91fc8454b058a11d4b8b797fa3064e32af12be81d2d927d6e",
42
42
  "frqtl/objects/ObjectFactory.js": "a8f050ce61b7ab9c9b4f81e9eeaebf05c3cc0b532c741ce93d6e952489772b11",
43
- "frqtl/objects/RecipeContext.js": "4d53e1d8a48f4567aeb882abac6f46cd649a51a3af4b543a6bec7ce74e77d7a8",
43
+ "frqtl/objects/RecipeContext.js": "4dafe289b2a5f768529d340e0d3876ddd8c72b4c295a37c00f77efe02912fc7c",
44
44
  "frqtl/objects/collectors/DataCollector.js": "8736d6cb77eec36ce2adcf2bc3b0d2436eeb70a73b77182ff053efdc6ca071df",
45
45
  "frqtl/objects/constants/WallBehaviors.js": "a05c6e06923962e2cc2cf7204131e5d20bf83c5c8e85fbb13867872040345e40",
46
46
  "frqtl/objects/geometry/SlabFace.js": "847921b124d3bc1692e88c363c087d392a1baa7c652a3a61e381d0f8a8342e57",
@@ -73,7 +73,7 @@
73
73
  "frqtl/objects/objects/slitLinerScatter.js": "9413cc7efa2132c68d7b4d5030af28d92e8d085e127d5c8e1568d0028da2fc13",
74
74
  "frqtl/objects/objects/sustainedSource.js": "0c324cef1583226d090b4d1014c2576ef8237f1bad47c1511bed3c3f81e04206",
75
75
  "frqtl/objects/objects/tagger.js": "e2a5ef9ae68466afb6b157ca6d7ce03a6f126d8c91c9f9d4be6dba8e928535eb",
76
- "frqtl/objects/record/Record.js": "5555c62e278654d1d54e2e3d89da4100de2d0ccae9563e62dfe4082ec7b543ba",
76
+ "frqtl/objects/record/Record.js": "fea53f9ae9c72e216f1e9ca7e2035c093e3135ccd132918ff6abd299fac6057b",
77
77
  "frqtl/objects/record/open-record.js": "2da2abcccd3ad5bc39eb7fcfb764601cd031dd0afa9b3c9cb96b0669621827df",
78
78
  "frqtl/objects/schema/engine-scopes.js": "d9d07914f748f9e7f86cc68e4d49da3b07d2bce37fea5280a88e6862ef3bb619",
79
79
  "frqtl/objects/schema/index.js": "74ba0b6a6e057cfd40edf2ce41fcce7206c0204e90eeee4a8db3b711f10ffd33",
@@ -112,21 +112,21 @@
112
112
  "frqtl/parts/DetectorWall.js": "d39ab88056fa28c322863430f8bad9704879e5d8149f1ac65adf1add057996f5",
113
113
  "frqtl/parts/FrqtlControlOverlay.js": "f862b1529265224aa96878b56b0c1c6e65683ba3fd6b517df86e17e92147f9c4",
114
114
  "frqtl/parts/FrqtlControlsPanel.js": "792d03dea218bdc335ba7e04362be620edbfa331bed9de087d61942299728cc1",
115
- "frqtl/parts/FrqtlVision.js": "118d0874249fa55783b196fd01297b3347951267a4add93381433a72c46e8622",
116
- "frqtl/parts/GraphingDetectorWall.js": "83aba36a6a6c15f92c99bbe5d7e63b12b38e07be65e02e89020c065514ef5b3d",
115
+ "frqtl/parts/FrqtlVision.js": "217472e195ccd588cccd8be9fe9419836b66c96c20509fa7d4320a57116bb0b3",
116
+ "frqtl/parts/GraphingDetectorWall.js": "a2fef24c2ef00416792c4b46a09c9544601e45e84593fa2287d2311f27a1bcee",
117
117
  "frqtl/parts/LargeObject.js": "d6cae738b30965f3eba4be4399fb4410f17afc3267b51e3ad9e1ba7423e17922",
118
118
  "frqtl/parts/LightColor.js": "8637e5a09bbad3a4fdd2034ccb44dfc1fda743e29b12b4c685a64b9e1df28daa",
119
- "frqtl/parts/LiveChart.js": "8bd0b862d2f7a4e818ae9cfc47e5e0f56eb97ea0a00fb836bba3f2374c12a1ce",
119
+ "frqtl/parts/LiveChart.js": "5d31947db9909e7a85dcbd99560e49b3f24ad60aea13f24b736a04d45998f060",
120
120
  "frqtl/parts/LogicGate.js": "05716711db1794ce9e29027ea16af083e5a1cc8b0657dce853cb6d2f6ee1fc02",
121
121
  "frqtl/parts/OvenBox.js": "7e393dd736c6ae410d85e5fe3edbea8677e5834bd1fe7b19d4e8f46af13764ac",
122
122
  "frqtl/parts/PointSource.js": "57b3b8353cba8c5ebfff6653e39948c9f0ee240449b4af08247687d3d9d9f092",
123
123
  "frqtl/parts/Rectangle.js": "8be4cae13135c3cb185d025dfd10d3a03b21d5627bfa92363c2faa021f751571",
124
124
  "frqtl/parts/SlitLightSource.js": "89a17aaade03e2e961af4181e6c3fd479d31c2c3298c46abc477a13f26d7a07a",
125
125
  "frqtl/parts/SlitScreen.js": "bdcaa761d0aa68c415a06ab2c523a8d4553a4c48cee00fe33d5df994adcf599a",
126
- "frqtl/parts/SphereEmitter.js": "645664f5a20924b777d9b4ad6fef6338e7de251d62c7f8a4c93e6b0f97b47d4a",
126
+ "frqtl/parts/SphereEmitter.js": "bf801404c67ff26dbf61353ea617fcefcff5a546c2e62f4d4afb95e9781c7a5e",
127
127
  "frqtl/parts/Trace.js": "f9d91bb9435bc5fabfd7bfdbfbf3f3ae34e2338090251d9e9d6c11abc2ed25fb",
128
- "frqtl/parts/Tunnel.js": "66bfca3d3d88ebf9a8cb7aa248b92cfc7d78a278449830b03e6b2700a21df891",
129
- "frqtl/parts/Wall.js": "7dcb520daee3facad8698b363bb49557003439dc5841706b105bd1acabc2e0bf",
128
+ "frqtl/parts/Tunnel.js": "7a7c3e52caa5f018178c596d735e6062c4cac1545d014216d3027ba9f457d07d",
129
+ "frqtl/parts/Wall.js": "7af7a41f7c8348deee1237a20e37e9c0e69cd3669ab315efaa8eeac03fbbccb8",
130
130
  "frqtl/parts/index.js": "70b8e227e09663889f4c7942d385f843d977579765723f829880fd6e9868ac53",
131
131
  "frqtl/scenes/absorbing-wall-3d.js": "722bb433142833f03150052b95039686aba0d9db510a5ff13fad95b5f17e0cf8",
132
132
  "frqtl/scenes/absorbing-wall.js": "0a47ca64e56481841339cd5882cf0e54ec7c14d87a4306d016ead1ff26591197",
@@ -141,13 +141,13 @@
141
141
  "frqtl/scenes/dse-one-emitter.js": "619892663838e0f018b9ade83c9994fb0709b9ed9904c92e41470a7b7fdd396f",
142
142
  "frqtl/scenes/dse-one-slit.js": "7bf299e3cc64dd3d1f8df805eec63d98d36b8592d55e3ef478d46c38bf674812",
143
143
  "frqtl/scenes/dse-photoelectric.js": "e0402c4508a4d97d3d9461f889b10a49da82ef5f232d81455c5e482db8b0bda6",
144
- "frqtl/scenes/dse-tunnel-rebuild.js": "c662bff31fb66c1f136b6f35d18943c58e97456b61fbab9f4cdb812e3acb2630",
144
+ "frqtl/scenes/dse-tunnel-rebuild.js": "c2721b4460dbe2f38e7e3bcff6f8498fd798c3404b807931b288ae2ccd43eec5",
145
145
  "frqtl/scenes/dse-two-emitters.js": "74cf1d4b226b0ce4e337697a0327de9b95190159b2554f53e629d89525321877",
146
146
  "frqtl/scenes/dse-two-slits.js": "f303a848d9c463a5a9443c380b257795b19127b122bdd2fcb28855841b219939",
147
147
  "frqtl/scenes/empty.js": "f3c2161f7bbc1be2d7fba0f117ac16b9e60966d9448877ead4037e3cb4fee678",
148
148
  "frqtl/scenes/laser-buildup.js": "f4b29b2eeeca730642f5adf2f499f6594b73f6e51ba6aa9a237f4ee643c5cf23",
149
149
  "frqtl/scenes/mirror-3d.js": "79ac794dfbc608037936908a9fe58ca34be93be6263d5940bb38129d55bb7943",
150
- "frqtl/scenes/mirror-route.js": "007af9eb382a1392eb22b6d81f7c3e40bffe7ba6ac53363ce7726ccb62b838c4",
150
+ "frqtl/scenes/mirror-route.js": "1407dd7a39c0efcd5ea0f028d67299c1ca4153c1c0cab2bb36d10416f1129931",
151
151
  "frqtl/scenes/mirror.js": "c7bcd440e9a78e333fd81d906b3788a62342910f46cf98de860098fcc1e0ba91",
152
152
  "frqtl/scenes/omni-3d.js": "d420a7aae9fdfd394b9288ce1f0256416e1b07f70bc570a928a4592c32a47c27",
153
153
  "frqtl/scenes/omni-source.js": "4f9aaa0e0ccbf4d18908d1628688dbeae376b535318f0cf969d61d8ea8f90b83",
@@ -166,12 +166,13 @@
166
166
  "shim/engine/artifact-shell.js": "f23c327500f8174b12d464868f6ec9c1701e7a9a5faa1c43a2ddafc3191dc38c",
167
167
  "shim/engine/boot-phases.js": "c6e12e12824bcf6f1a289f46e41c898a5416c80c131594525145aae9a3eefbfa",
168
168
  "shim/engine/engine-stack-boot.js": "0abf57dcff2ca2bb025c787a17875d49d54d9218ac08c78cb9c678dbb6118d08",
169
- "shim/engine/router.js": "62a1995871557536f48903eec14849b135f7d5d5e68c77139b61289a405072b3",
169
+ "shim/engine/router.js": "a2582ce456f5cee9ac4d9d2117afdf4dce59e7e3e009f483dbd0f8d0a4c3cb8e",
170
170
  "shim/engine/site-graph.js": "b0d2569c67516d08bba17a152d5d3bfa0b036666eda7f11df74b4d68854e1b93",
171
171
  "shim/experiment-frame/index.js": "5a0219aaa225d8f808c405b08623ce85ada37d83fb4f55f983db3303e431e47f",
172
172
  "shim/expr/ast-schema.js": "4f1133b887639bbd86fab976292323bf4a955cdc873ff7ca8e8a8eb3b416923f",
173
- "shim/field-descriptor/index.js": "6647d57d1523519114adc0a5cb66335ec5121c39ab294b9c97bdfb9e5d7f61e0",
173
+ "shim/field-descriptor/index.js": "050091a9cab0bd37b32b5d8a52fc9bca3b42a089c188d7e49a11cf88b5c069f7",
174
174
  "shim/frame-data/index.js": "df6957b0b4a8e0ee0035bc970647b90089b421ae7f05911cc047d48457164be6",
175
+ "shim/host/device.js": "7dd8d069463141e4f8bdc8827d747c0a9e27ad7ec1d2c6feaaad6e58f0bed512",
175
176
  "shim/host/host-view.js": "feec2f5fdc318e75cd01768c24891f85d2694a269696fcfffd9567d31983cf43",
176
177
  "shim/host/index.js": "fade4b171218965570aa2e67e841920ebb3d972ff15e1cfa0394fd3fa123d82c",
177
178
  "shim/index.js": "96165c6161ffddbace0be0cde0b3a83943298531537dd2cc9c6e281b8274ff19",
@@ -197,31 +198,31 @@
197
198
  "shim/page-params/index.js": "c58732b8127da8c2b4ecfd922bf46a6c9a5e9ab7017f41c095a6ec937011f0fa",
198
199
  "shim/project/fork.js": "07555f24c7e6d1bbe723853bc09ad570b9cd36cef248a07e98e7398d41ac70da",
199
200
  "shim/project/identity.js": "8d3e0bb9df75eecf7d11a1ea7948ff66c559973a891574d0b208c9e8f8faf504",
200
- "shim/project/index.js": "ba0d7812445aad52c36d6dd806e2d8c6b87d43e4d7c4494e9c0bfa9da8503a03",
201
+ "shim/project/index.js": "5121dd4c18aeea190d2a998fa7a13f86cb36b13cf043f8c549963c1ae51ba26e",
201
202
  "shim/project/link.js": "0e3fc878bc45d9916baacd7cf129687ec6e82efd4f6f5036e4697180b3d0f87a",
202
203
  "shim/project/manifest.js": "442267af07ae1b977ff6aeaca49d98ce82b77f548cfdd1ed0f258b3429d75f31",
203
204
  "shim/project/node-specifier-hook.mjs": "8601edf762bd9cd011632bc681d0589dfc77368cda0e994125d0eb123fea89d0",
204
205
  "shim/project/parts-module.js": "b08e0149f086640692e1b2ff8e0d7ae0379c2bc934a0891418de5c2b8c0e8e60",
205
- "shim/project/parts-spelling.js": "f43c356d8c08077a76190cb88bad4daa4280c112d120ee478ba67993c77c41ea",
206
+ "shim/project/parts-spelling.js": "4cc7b8a2f0aa1f3ad4c1c3cf15f74b2860a94ae2214dbef78d31c85ff8916cfe",
206
207
  "shim/project/parts-writer.js": "121fa67261d6f366190c9ac4961fceed2f26ca7f35cf3fbffc767829afc69978",
207
208
  "shim/project/read-text.js": "b89bd70e3a271e1f42c8ce9427a20cdd9a4c4c9c31b64c49ff1f56b095620136",
208
- "shim/project/record-format.js": "95cbb99d294e66c94f9f1921664cd932649c6d6b187b17928b8fb4b955ca880f",
209
+ "shim/project/record-format.js": "87d5cc2eae8ff600db1db58a0e0047819f7c9839ff702acdedc366c7647eb0ef",
209
210
  "shim/project/register-node.mjs": "9b408da7dc1b11d16237cd62caca281c823ea80173d386ad35e41ea17c34857f",
210
211
  "shim/project/specifiers.js": "288e5e06e53b873449190f6f85855177a72e52623bd11eef016acafb8095d829",
211
212
  "shim/registry/copy-gate.js": "cfbf52669c849b8837b326f2743458f1ead0720cad51a6c6d409c9bd6c7f9495",
212
- "shim/registry/exhibits.js": "e137951429929dae6c38e996546b66dd19b575ee4f0aafbddf28096c847a27fc",
213
+ "shim/registry/exhibits.js": "5a2f085c8a6b012114796897ec284f2bdd5081936b4e474d6227318c67dfb48a",
213
214
  "shim/registry/index.js": "3a96c6b8790058663bb3157f6daecbcc199c2472d096b413e1766dd279832c05",
214
215
  "shim/registry/scene-roster.js": "318fe373f71b1c58d05425693f2fad4bd5368f6095b65817c6d637430a0bfb73",
215
216
  "shim/registry/scene-source.js": "c9c4b48c24d4b1ebee1549a7891a1ba708909b94b9403ce28eefbd902cacf49f",
216
217
  "shim/registry/showcase-link.js": "daf5c1426aeb3c578bace5e0edea9ba2538cd82336d2cdc944e5fed6decd7551",
217
- "shim/registry/studio-landing.js": "c34c9e7e77087c2bfa6be9c34dafedb1e3256e92eb1aeb629b7f7182cd2edf9f",
218
- "shim/registry/walkthrough.js": "8f8b6f59e9e81c67c9c62f8fb271eee2e4d6e1fba6e116b227a2f1e3f9930230",
218
+ "shim/registry/studio-landing.js": "bd81ad9333332de4a29b3121f963edd354450c75c97f4f43170a98c2dcceb7f3",
219
+ "shim/registry/walkthrough.js": "439e03fc6c85e5982a98049118dc89f33c498778c1f3349bbbe8f60d64761071",
219
220
  "shim/render-state/apply.js": "94a9e60cb1722c9a82aa00f2f161fd65e6f438147333856a786e42a4ad5b7064",
220
221
  "shim/render-state/index.js": "72a6a3dcd8037893cb8fbd7ef32b126770e7efaec70847f8e4f2915ddb690b43",
221
- "shim/render/capture-envelope.js": "2667a355169bb124fd8117e9cbf1d630085257b9f6fa537362f12d0aa649da2d",
222
+ "shim/render/capture-envelope.js": "74db53dbc8c87d1b52452bcb858b1cc5210593062fec0b98609cd59cab63777a",
222
223
  "shim/render/heat-colormap.js": "0eadc758179024734c45eaeffd8f5b44e7c4102b7042767a4a5b66541b3bbd7c",
223
224
  "shim/render/render-stages.js": "baf674c205df7638af53e51ac77152a448fe6bf6d4ea51d58ac565556cd38da7",
224
- "shim/render/sun-look.js": "f8f23f0853a9c22459fff866ccf59ab00332697d48fd529fca5c2e8554a2d83d",
225
+ "shim/render/sun-look.js": "95cf56ee8a5c8da107323bf0dd33568e68e2a8bb2d9a5d61a3453a1b9d28ba2c",
225
226
  "shim/rpc/index.js": "56af3afeed22e8d718cd2f7a8ad70db8745f54dbc139af9d71f1d73e2ca37698",
226
227
  "shim/runtime/index.js": "92bb4d8b5f203f949e441781b864efe5c6748df14a3a8f306e4c0bc11c7f6d35",
227
228
  "shim/storage/boot.js": "689504e15dc075e32d08abf61c75a45b1d565c09f086b42400dae40d8186c411",
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * godrays-layer.js — the three provider's god-rays layer: WHAT the rays are computed from
5
- * (the source rule), the light position, and the radial march + composite passes.
5
+ * (the source rule, `godraySource`), HOW (`godrayMode`: streak | field | radial), and the passes.
6
6
  *
7
7
  * God-rays are a render effect computed from the density of the LIGHT particles: by default
8
8
  * (`godraySource: 'leaf'`) the frames of the fundamental (lowest) layer. A parent frame at a higher layer is drawn in the scene
@@ -96,6 +96,140 @@
96
96
  return out;
97
97
  }
98
98
 
99
+ /**
100
+ * The `godrayMode` render option — the ONE owner of its values and default. HOW the rays are
101
+ * computed from the source frames:
102
+ * 'streak' (default) — each source frame trails a short streak BEHIND it along its own
103
+ * projected direction of travel (FrameData vx,vy,vz: the mean axis);
104
+ * 'field' — every source frame's projected direction is splatted into a
105
+ * direction grid; each pixel's march steps UPSTREAM along the local
106
+ * direction, so the shafts extend ahead of the light and bend with the flow;
107
+ * 'radial' — every pixel marches toward ONE light point (`lightUV`).
108
+ * `godrayDensity` is the reach in every mode: the march's fraction of the distance to the
109
+ * light (radial), the streak length (streak), the march length (field).
110
+ */
111
+ var GODRAY_MODES = ['streak', 'field', 'radial'];
112
+ var GODRAY_MODE_DEFAULT = 'streak';
113
+
114
+ /**
115
+ * The effective mode for a bag value: unset → the default; anything not declared THROWS.
116
+ * @param {*} value — opts.godrayMode
117
+ * @returns {'streak'|'field'|'radial'}
118
+ */
119
+ function resolveGodrayMode(value) {
120
+ if (value === undefined || value === null) { return GODRAY_MODE_DEFAULT; }
121
+ if (GODRAY_MODES.indexOf(value) === -1) {
122
+ throw new Error('GodraysLayer: godrayMode ' + JSON.stringify(value) + ' is not one of [' +
123
+ GODRAY_MODES.join(', ') + '] (default ' + JSON.stringify(GODRAY_MODE_DEFAULT) + ').');
124
+ }
125
+ return value;
126
+ }
127
+
128
+ // Screen-fraction scales (of the screen's SHORT side) the density knob multiplies.
129
+ var STREAK_REACH = 0.06; // streak length = godrayDensity × this
130
+ var STREAK_WIDTH = 0.004; // streak width (floor 1.5 physical px)
131
+ var STREAK_GAIN = 0.6; // streak brightness relative to the source pixel at the head
132
+ var FIELD_REACH = 0.25; // field march length = godrayDensity × this
133
+ var FIELD_CELLS = 96; // direction-grid columns along the LONG side
134
+ var FIELD_RADIUS = 3; // splat radius in grid cells (tent weight)
135
+ var MARCH_SAMPLES = 50; // steps per pixel in the radial and field marches
136
+
137
+ /**
138
+ * Each frame's head in screen UV and its projected direction of travel as a UNIT vector in
139
+ * PIXEL space (aspect-corrected). The direction is the projection of (pos + d̂) minus the
140
+ * projection of pos, d̂ = normalize(vx,vy,vz). A frame with no direction, one whose direction
141
+ * projects to under 1e-6 px (along the view axis), or one outside the clip volume yields NO
142
+ * entry: it still draws in the scene as itself, a dot with no streak and no flow.
143
+ * @param {Array<object>} frames — FrameData (rect.x, rect.y, z, vx, vy, vz)
144
+ * @param {Function} project — (x,y,z) → NDC {x,y,z}
145
+ * @param {number} w @param {number} h — the target's pixel size
146
+ * @returns {{count:number, u:Float32Array, v:Float32Array, dx:Float32Array, dy:Float32Array}}
147
+ */
148
+ function screenDirections(frames, project, w, h) {
149
+ var n = frames.length;
150
+ var out = { count: 0, u: new Float32Array(n), v: new Float32Array(n), dx: new Float32Array(n), dy: new Float32Array(n) };
151
+ for (var i = 0; i < n; i++) {
152
+ var f = frames[i];
153
+ var vx = f.vx || 0, vy = f.vy || 0, vz = f.vz || 0;
154
+ var len = Math.sqrt(vx * vx + vy * vy + vz * vz);
155
+ if (len === 0) { continue; }
156
+ var x = f.rect.x, y = f.rect.y, z = f.z || 0;
157
+ var p0 = project(x, y, z), x0 = p0.x, y0 = p0.y, z0 = p0.z;
158
+ if (!(Math.abs(x0) <= 1 && Math.abs(y0) <= 1 && Math.abs(z0) <= 1)) { continue; }
159
+ var p1 = project(x + vx / len, y + vy / len, z + vz / len);
160
+ var px = (p1.x - x0) * w / 2, py = (p1.y - y0) * h / 2;
161
+ var pl = Math.sqrt(px * px + py * py);
162
+ if (!(pl > 1e-6)) { continue; }
163
+ var k = out.count++;
164
+ out.u[k] = x0 * 0.5 + 0.5; out.v[k] = y0 * 0.5 + 0.5;
165
+ out.dx[k] = px / pl; out.dy[k] = py / pl;
166
+ }
167
+ return out;
168
+ }
169
+
170
+ /**
171
+ * Screen-space streak quads (two triangles each), in NDC: the head at the frame, the tail
172
+ * `lengthPx` BEHIND it (opposite its direction), `widthPx` wide. Per vertex: `head` (the head's
173
+ * UV, where the colour is sampled) and `fade` (1 at the head, 0 at the tail).
174
+ * @param {object} dirs — screenDirections(...)
175
+ * @returns {{count:number, position:Float32Array, head:Float32Array, fade:Float32Array}}
176
+ */
177
+ function streakQuads(dirs, lengthPx, widthPx, w, h) {
178
+ var n = dirs.count, pos = new Float32Array(n * 18), head = new Float32Array(n * 12), fade = new Float32Array(n * 6);
179
+ var sx = 2 / w, sy = 2 / h, half = widthPx / 2;
180
+ for (var i = 0; i < n; i++) {
181
+ var hx = dirs.u[i] * 2 - 1, hy = dirs.v[i] * 2 - 1, dx = dirs.dx[i], dy = dirs.dy[i];
182
+ var tx = hx - dx * lengthPx * sx, ty = hy - dy * lengthPx * sy;
183
+ var nx = -dy * half * sx, ny = dx * half * sy;
184
+ // head+n, head-n, tail+n ; tail+n, head-n, tail-n
185
+ var vs = [hx + nx, hy + ny, 1, hx - nx, hy - ny, 1, tx + nx, ty + ny, 0,
186
+ tx + nx, ty + ny, 0, hx - nx, hy - ny, 1, tx - nx, ty - ny, 0];
187
+ for (var j = 0; j < 6; j++) {
188
+ var o = i * 6 + j;
189
+ pos[o * 3] = vs[j * 3]; pos[o * 3 + 1] = vs[j * 3 + 1]; pos[o * 3 + 2] = 0;
190
+ fade[o] = vs[j * 3 + 2];
191
+ head[o * 2] = dirs.u[i]; head[o * 2 + 1] = dirs.v[i];
192
+ }
193
+ }
194
+ return { count: n * 6, position: pos, head: head, fade: fade };
195
+ }
196
+
197
+ /**
198
+ * The direction field: a `gw × gh` RGBA grid (row 0 = bottom, UV v = 0). Each direction is
199
+ * splatted with a tent weight over `radius` cells; a cell stores its normalized mean direction
200
+ * in R,G (0.5 + 0.5·d̂) and in B the strength a = coherence × coverage (coherence = |mean|,
201
+ * 0 where opposed flows cancel; coverage = min(1, Σweight)). Decode: d = (RG·2 − 1)·B.
202
+ * @param {object} dirs — screenDirections(...)
203
+ * @returns {{width:number, height:number, data:Uint8Array}}
204
+ */
205
+ function buildDirectionField(dirs, gw, gh, radius) {
206
+ var cells = gw * gh, sx = new Float32Array(cells), sy = new Float32Array(cells), sw = new Float32Array(cells);
207
+ for (var i = 0; i < dirs.count; i++) {
208
+ var cx = dirs.u[i] * gw - 0.5, cy = dirs.v[i] * gh - 0.5;
209
+ var i0 = Math.max(0, Math.ceil(cx - radius)), i1 = Math.min(gw - 1, Math.floor(cx + radius));
210
+ var j0 = Math.max(0, Math.ceil(cy - radius)), j1 = Math.min(gh - 1, Math.floor(cy + radius));
211
+ for (var j = j0; j <= j1; j++) {
212
+ for (var ii = i0; ii <= i1; ii++) {
213
+ var ex = ii - cx, ey = j - cy, wt = 1 - Math.sqrt(ex * ex + ey * ey) / (radius + 1);
214
+ if (wt <= 0) { continue; }
215
+ var c = j * gw + ii;
216
+ sx[c] += dirs.dx[i] * wt; sy[c] += dirs.dy[i] * wt; sw[c] += wt;
217
+ }
218
+ }
219
+ }
220
+ var data = new Uint8Array(cells * 4);
221
+ for (var k = 0; k < cells; k++) {
222
+ var r = 0.5, g = 0.5, a = 0;
223
+ if (sw[k] > 0) {
224
+ var mx = sx[k] / sw[k], my = sy[k] / sw[k], m = Math.sqrt(mx * mx + my * my);
225
+ if (m > 1e-6) { r = 0.5 + 0.5 * mx / m; g = 0.5 + 0.5 * my / m; a = m * Math.min(1, sw[k]); }
226
+ }
227
+ data[k * 4] = Math.round(r * 255); data[k * 4 + 1] = Math.round(g * 255);
228
+ data[k * 4 + 2] = Math.round(a * 255); data[k * 4 + 3] = 255;
229
+ }
230
+ return { width: gw, height: gh, data: data };
231
+ }
232
+
99
233
  // R4-B god-rays — radial light-scattering march (adapted from abberg's
100
234
  // VolumetericLightShader / GPU Gems 3). For each pixel it steps TOWARD the
101
235
  // projected light position, accumulating SOURCE brightness with exponential
@@ -110,7 +244,7 @@
110
244
  'uniform float uWeight;',
111
245
  'uniform float uExposure;',
112
246
  'varying vec2 vTexCoord;',
113
- 'const int SAMPLES = 50;',
247
+ 'const int SAMPLES = ' + MARCH_SAMPLES + ';',
114
248
  'void main() {',
115
249
  ' vec2 tc = vTexCoord;',
116
250
  ' vec2 delta = (tc - uLightPos) * (1.0 / float(SAMPLES)) * uDensity;',
@@ -126,6 +260,64 @@
126
260
  '}'
127
261
  ].join('\n');
128
262
 
263
+ // The 'field' march: the radial march's accumulation (same decay/weight/exposure), but each
264
+ // step reads the LOCAL direction from the field and steps upstream along it, so a pixel
265
+ // collects the light that flowed into it. An empty cell (strength 0) does not step.
266
+ var GODRAY_FIELD_FRAG = [
267
+ 'uniform sampler2D uScene;',
268
+ 'uniform sampler2D uField;',
269
+ 'uniform vec2 uStepUV;', // one step's length in UV per unit pixel-space direction
270
+ 'uniform float uDecay;',
271
+ 'uniform float uWeight;',
272
+ 'uniform float uExposure;',
273
+ 'varying vec2 vTexCoord;',
274
+ 'const int SAMPLES = ' + MARCH_SAMPLES + ';',
275
+ 'void main() {',
276
+ ' vec2 tc = vTexCoord;',
277
+ ' vec3 rays = vec3(0.0);',
278
+ ' float decay = 1.0;',
279
+ ' for (int i = 0; i < SAMPLES; i++) {',
280
+ ' vec3 f = texture2D(uField, tc).rgb;',
281
+ ' tc -= (f.rg * 2.0 - 1.0) * f.b * uStepUV;',
282
+ ' vec3 s = texture2D(uScene, tc).rgb;',
283
+ ' rays += s * decay * uWeight;',
284
+ ' decay *= uDecay;',
285
+ ' }',
286
+ ' gl_FragColor = vec4(rays * uExposure, 1.0);',
287
+ '}'
288
+ ].join('\n');
289
+
290
+ // The 'streak' quads: positions are NDC already; colour = the source image AT THE HEAD,
291
+ // fading to black at the tail, added (additive blending) into a black rays target.
292
+ var GODRAY_STREAK_VERT = [
293
+ 'attribute vec2 aHead;',
294
+ 'attribute float aFade;',
295
+ 'varying vec2 vHead;',
296
+ 'varying float vFade;',
297
+ 'void main() {',
298
+ ' vHead = aHead; vFade = aFade;',
299
+ ' gl_Position = vec4(position.xy, 0.0, 1.0);',
300
+ '}'
301
+ ].join('\n');
302
+ // The head colour is the brightest texel of the 3×3 around the head: a sub-pixel quantum lands
303
+ // between texels, so a single tap at its centre reads it dimmed.
304
+ var GODRAY_STREAK_FRAG = [
305
+ 'uniform sampler2D uSource;',
306
+ 'uniform vec2 uTexel;',
307
+ 'uniform float uGain;',
308
+ 'varying vec2 vHead;',
309
+ 'varying float vFade;',
310
+ 'void main() {',
311
+ ' vec3 c = vec3(0.0);',
312
+ ' for (int i = -1; i <= 1; i++) {',
313
+ ' for (int j = -1; j <= 1; j++) {',
314
+ ' c = max(c, texture2D(uSource, vHead + vec2(float(i), float(j)) * uTexel).rgb);',
315
+ ' }',
316
+ ' }',
317
+ ' gl_FragColor = vec4(c * vFade * uGain, 1.0);',
318
+ '}'
319
+ ].join('\n');
320
+
129
321
  // R4-B composite — unblurred scene + additive rays * intensity knob.
130
322
  var GODRAY_COMP_FRAG = [
131
323
  'uniform sampler2D uScene;',
@@ -140,17 +332,19 @@
140
332
  ].join('\n');
141
333
 
142
334
  /**
143
- * The live passes, created lazily by the provider on first activation.
335
+ * The live passes, created lazily by the provider on first activation. Every mode ends in the
336
+ * same composite (sceneRT + rays × intensity → screen); they differ in how rtRays is made.
144
337
  * @param {object} THREE
145
338
  * @param {string} quadVert — the provider's full-screen-quad vertex shader
146
339
  * @param {number} w @param {number} h — the provider's physical size
147
- * @returns {{rtSource:object, setSize:Function, lightUV:Function, march:Function}}
340
+ * @returns {{rtSource:object, setSize:Function, lightUV:Function, march:Function, streak:Function, field:Function}}
148
341
  */
149
342
  function makeGodrayPasses(THREE, quadVert, w, h) {
150
343
  var fxScene = new THREE.Scene();
151
344
  var fxQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), null);
152
345
  fxScene.add(fxQuad);
153
- var vLight = new THREE.Vector3();
346
+ var vLight = new THREE.Vector3(), vProj = new THREE.Vector3(), clearKeep = new THREE.Color();
347
+ var size = { w: w, h: h };
154
348
  var blurMat = new THREE.ShaderMaterial({
155
349
  uniforms: {
156
350
  uScene: { value: null },
@@ -166,9 +360,49 @@
166
360
  uniforms: { uScene: { value: null }, uRays: { value: null }, uIntensity: { value: 1.0 } },
167
361
  vertexShader: quadVert, fragmentShader: GODRAY_COMP_FRAG, depthWrite: false, depthTest: false
168
362
  });
363
+ var fieldMat = new THREE.ShaderMaterial({
364
+ uniforms: {
365
+ uScene: { value: null },
366
+ uField: { value: null },
367
+ uStepUV: { value: new THREE.Vector2(0, 0) },
368
+ uDecay: blurMat.uniforms.uDecay, // the SAME accumulation as the radial march
369
+ uWeight: blurMat.uniforms.uWeight,
370
+ uExposure: blurMat.uniforms.uExposure
371
+ },
372
+ vertexShader: quadVert, fragmentShader: GODRAY_FIELD_FRAG, depthWrite: false, depthTest: false
373
+ });
374
+ var fieldTex = null;
375
+
376
+ var streakScene = new THREE.Scene();
377
+ var streakGeo = new THREE.BufferGeometry(), streakCap = 0;
378
+ var streakMat = new THREE.ShaderMaterial({
379
+ uniforms: { uSource: { value: null }, uTexel: { value: new THREE.Vector2(1 / w, 1 / h) }, uGain: { value: STREAK_GAIN } },
380
+ vertexShader: GODRAY_STREAK_VERT, fragmentShader: GODRAY_STREAK_FRAG,
381
+ blending: THREE.AdditiveBlending, transparent: true, depthWrite: false, depthTest: false,
382
+ side: THREE.DoubleSide // a quad's winding follows its direction; never cull it
383
+ });
384
+ var streakMesh = new THREE.Mesh(streakGeo, streakMat);
385
+ streakMesh.frustumCulled = false;
386
+ streakScene.add(streakMesh);
387
+
388
+ function composite(renderer, quadCamera, sceneRT, rtRays, intensity) {
389
+ compMat.uniforms.uScene.value = sceneRT.texture;
390
+ compMat.uniforms.uRays.value = rtRays.texture;
391
+ compMat.uniforms.uIntensity.value = intensity;
392
+ fxQuad.material = compMat;
393
+ renderer.setRenderTarget(null);
394
+ renderer.render(fxScene, quadCamera);
395
+ }
396
+ // The reach in every mode is the density knob; unset → the value the radial march holds.
397
+ function densityOf(density) { return (typeof density === 'number') ? density : blurMat.uniforms.uDensity.value; }
398
+ function directionsOf(frames, camera) {
399
+ return screenDirections(frames, function (x, y, z) { return vProj.set(x, y, z).project(camera); }, size.w, size.h);
400
+ }
401
+ function shortSide() { return Math.min(size.w, size.h); }
402
+
169
403
  var passes = {
170
404
  rtSource: new THREE.WebGLRenderTarget(w, h), // the SOURCE image the provider renders into
171
- setSize: function (sw, sh) { passes.rtSource.setSize(sw, sh); },
405
+ setSize: function (sw, sh) { size.w = sw; size.h = sh; passes.rtSource.setSize(sw, sh); },
172
406
  // Light position in [0,1] screen UV: the fullness-weighted centroid of the SOURCE
173
407
  // frames, projected through `camera` (2D ortho + 3D persp); screen centre when none.
174
408
  lightUV: function (tickData, camera, source) {
@@ -183,7 +417,7 @@
183
417
  vLight.set(sx / sw, sy / sw, sz / sw).project(camera); // → NDC [-1,1]
184
418
  return new THREE.Vector2(vLight.x * 0.5 + 0.5, vLight.y * 0.5 + 0.5);
185
419
  },
186
- // Pass 2: march sourceRT → rtRays. Pass 3: sceneRT + rays × intensity → screen.
420
+ // 'radial' — Pass 2: march sourceRT toward lightUV → rtRays. Pass 3: composite.
187
421
  march: function (renderer, quadCamera, sceneRT, sourceRT, rtRays, lightUV, density, intensity) {
188
422
  blurMat.uniforms.uScene.value = sourceRT.texture;
189
423
  blurMat.uniforms.uLightPos.value.copy(lightUV);
@@ -191,12 +425,61 @@
191
425
  fxQuad.material = blurMat;
192
426
  renderer.setRenderTarget(rtRays);
193
427
  renderer.render(fxScene, quadCamera);
194
- compMat.uniforms.uScene.value = sceneRT.texture;
195
- compMat.uniforms.uRays.value = rtRays.texture;
196
- compMat.uniforms.uIntensity.value = intensity;
197
- fxQuad.material = compMat;
198
- renderer.setRenderTarget(null);
428
+ composite(renderer, quadCamera, sceneRT, rtRays, intensity);
429
+ },
430
+ // 'streak' — rtRays cleared to black, one fading quad per moving source frame trailing
431
+ // behind it (colour = sourceRT at its head), then the composite.
432
+ streak: function (renderer, quadCamera, sceneRT, sourceRT, rtRays, frames, camera, density, intensity) {
433
+ var q = streakQuads(directionsOf(frames, camera), densityOf(density) * STREAK_REACH * shortSide(),
434
+ Math.max(1.5, STREAK_WIDTH * shortSide()), size.w, size.h);
435
+ if (q.count > streakCap) {
436
+ streakCap = Math.max(q.count, streakCap * 2, 600);
437
+ streakGeo.setAttribute('position', new THREE.BufferAttribute(new Float32Array(streakCap * 3), 3));
438
+ streakGeo.setAttribute('aHead', new THREE.BufferAttribute(new Float32Array(streakCap * 2), 2));
439
+ streakGeo.setAttribute('aFade', new THREE.BufferAttribute(new Float32Array(streakCap), 1));
440
+ }
441
+ if (q.count > 0) {
442
+ streakGeo.attributes.position.array.set(q.position);
443
+ streakGeo.attributes.aHead.array.set(q.head);
444
+ streakGeo.attributes.aFade.array.set(q.fade);
445
+ streakGeo.attributes.position.needsUpdate = true;
446
+ streakGeo.attributes.aHead.needsUpdate = true;
447
+ streakGeo.attributes.aFade.needsUpdate = true;
448
+ }
449
+ streakGeo.setDrawRange(0, q.count);
450
+ streakMat.uniforms.uSource.value = sourceRT.texture;
451
+ streakMat.uniforms.uTexel.value.set(1 / size.w, 1 / size.h);
452
+ renderer.getClearColor(clearKeep);
453
+ var alphaKeep = renderer.getClearAlpha();
454
+ renderer.setClearColor(0x000000, 1);
455
+ renderer.setRenderTarget(rtRays);
456
+ renderer.clear();
457
+ if (q.count > 0) { renderer.render(streakScene, quadCamera); }
458
+ renderer.setClearColor(clearKeep, alphaKeep);
459
+ composite(renderer, quadCamera, sceneRT, rtRays, intensity);
460
+ },
461
+ // 'field' — the source frames' directions → a grid texture; march sourceRT along it →
462
+ // rtRays; then the composite.
463
+ field: function (renderer, quadCamera, sceneRT, sourceRT, rtRays, frames, camera, density, intensity) {
464
+ var long = Math.max(size.w, size.h);
465
+ var gw = Math.max(8, Math.round(FIELD_CELLS * size.w / long)), gh = Math.max(8, Math.round(FIELD_CELLS * size.h / long));
466
+ var grid = buildDirectionField(directionsOf(frames, camera), gw, gh, FIELD_RADIUS);
467
+ if (!fieldTex || fieldTex.image.width !== gw || fieldTex.image.height !== gh) {
468
+ if (fieldTex) { fieldTex.dispose(); }
469
+ fieldTex = new THREE.DataTexture(grid.data, gw, gh, THREE.RGBAFormat);
470
+ fieldTex.minFilter = THREE.LinearFilter; fieldTex.magFilter = THREE.LinearFilter;
471
+ } else {
472
+ fieldTex.image.data.set(grid.data);
473
+ }
474
+ fieldTex.needsUpdate = true;
475
+ var stepPx = densityOf(density) * FIELD_REACH * shortSide() / MARCH_SAMPLES;
476
+ fieldMat.uniforms.uScene.value = sourceRT.texture;
477
+ fieldMat.uniforms.uField.value = fieldTex;
478
+ fieldMat.uniforms.uStepUV.value.set(stepPx / size.w, stepPx / size.h);
479
+ fxQuad.material = fieldMat;
480
+ renderer.setRenderTarget(rtRays);
199
481
  renderer.render(fxScene, quadCamera);
482
+ composite(renderer, quadCamera, sceneRT, rtRays, intensity);
200
483
  }
201
484
  };
202
485
  return passes;
@@ -209,5 +492,11 @@ export {
209
492
  GODRAY_SOURCES,
210
493
  GODRAY_SOURCE_DEFAULT,
211
494
  resolveGodraySource,
495
+ GODRAY_MODES,
496
+ GODRAY_MODE_DEFAULT,
497
+ resolveGodrayMode,
498
+ screenDirections,
499
+ streakQuads,
500
+ buildDirectionField,
212
501
  makeGodrayPasses
213
502
  };
@@ -491,6 +491,12 @@ import * as RendererProviderContract from '../renderer-provider-contract.js';
491
491
  var prog = compileProgram(gl, VERT_SRC, fragSrc);
492
492
  if (!prog) return { skip: true };
493
493
 
494
+ // Both uploads (the scene canvas and FieldBuilder's field) are TOP row first, and
495
+ // vTexCoord has its origin at the BOTTOM-left (Filters.js convention). Flip on upload,
496
+ // once for this context, or the composite draws the whole picture upside down while
497
+ // screenToWorld (and every hand on the canvas) stays right side up.
498
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
499
+
494
500
  var quadBuf = gl.createBuffer();
495
501
  gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
496
502
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);