@descix/frqtl-node 0.6.0-p1.1 → 0.6.0-p4.1

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 (35) hide show
  1. package/CLAUDE.md +1 -1
  2. package/README.md +3 -3
  3. package/dist/dist-manifest.json +27 -25
  4. package/dist/eyes/index.js +5 -2
  5. package/dist/frqtl/objects/FrqtlApp.js +28 -1
  6. package/dist/frqtl/objects/RecipeContext.js +178 -12
  7. package/dist/frqtl/objects/instruments/hosted-eyes.js +200 -29
  8. package/dist/frqtl/objects/record/Record.js +326 -0
  9. package/dist/frqtl/objects/record/open-record.js +22 -0
  10. package/dist/frqtl/parts/FrqtlVision.js +21 -6
  11. package/dist/shim/bundle/index.js +112 -96
  12. package/dist/shim/bundle/indexHtml.js +66 -39
  13. package/dist/shim/engine/router.js +2 -2
  14. package/dist/shim/index.js +1 -1
  15. package/dist/shim/notebook/format.js +1 -1
  16. package/dist/shim/notebook/store.js +2 -3
  17. package/dist/shim/project/index.js +16 -3
  18. package/dist/shim/project/link.js +181 -43
  19. package/dist/shim/project/manifest.js +151 -0
  20. package/dist/shim/project/parts-writer.js +9 -5
  21. package/dist/shim/project/record-format.js +345 -0
  22. package/dist/shim/project/specifiers.js +1 -1
  23. package/dist/shim/registry/index.js +1 -1
  24. package/dist/shim/registry/scene-source.js +44 -30
  25. package/dist/shim/registry/walkthrough.js +4 -3
  26. package/dist/shim/storage/codec-chains.js +88 -0
  27. package/dist/shim/storage/folders.js +205 -0
  28. package/dist/shim/storage/index.js +64 -8
  29. package/dist/shim/storage/make-namespace-store.js +5 -5
  30. package/llms.txt +2 -2
  31. package/package.json +1 -1
  32. package/dist/shim/bundle/frqnb.js +0 -112
  33. package/dist/shim/bundle/manifest.js +0 -95
  34. package/dist/shim/storage/modules.js +0 -249
  35. package/dist/shim/storage/projects.js +0 -150
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
- Pre-release `0.6.0-p1.1` (npm dist-tag `next`). Node ≥ 22.12.
5
+ Pre-release `0.6.0-p4.1` (npm dist-tag `next`). Node ≥ 22.12.
6
6
 
7
7
  ## Install
8
8
  npm install @descix/frqtl-node@next
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
- Pre-release `0.6.0-p1.1` (npm dist-tag `next`). Node ≥ 22.12.
7
+ Pre-release `0.6.0-p4.1` (npm dist-tag `next`). Node ≥ 22.12.
8
8
 
9
9
  ## Install
10
10
 
@@ -31,8 +31,8 @@ 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`, `linkProject`, `sketchProject`, `SKETCH_ENTRY`, `projectIdentity` |
35
- | `./registry` | the walk-through rungs, the showcase roster, the experiment registry, `loadSceneSource` |
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` |
35
+ | `./registry` | the walk-through rungs, the showcase roster, the experiment registry, `projectOf` |
36
36
  | `./kinds` | `registerKind`, `kindNamed`, `kindOf`, `registeredKinds` |
37
37
  | `./register` | installs the resolve hook for a sketch's `@descix/frqtl-web/...` imports (`node --import @descix/frqtl-node/register`) |
38
38
  | `./shim/controls/registry.js`, `./shim/render/sun-look.js`, `./compat/renderer/display-slot.js`, `./compat/renderer/orbit-bag.js`, `./objects/setup/_collisionMatrixLanes.js`, `./compat/engine.js` | the six non-part modules a scene may import (the rows of `FRQTL_WEB_SPECIFIERS`) |
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@descix/frqtl-node",
3
- "version": "0.6.0-p1.1",
3
+ "version": "0.6.0-p4.1",
4
4
  "files": {
5
- "eyes/index.js": "c74d5595ea2f778f2f63ef3ce2b6568433cef80ec21b6d66901c1d1a942dbe9a",
5
+ "eyes/index.js": "b3563421030bb7fdd277eb697959fbe9107463cbb5478160edc7d887daa67be3",
6
6
  "fat/pkg-node/egpt_wasm.js": "5e6af1f6725c93c0e9ad7386f1785cbe639ce596a4caa19ca85729c1f2bea04d",
7
7
  "fat/pkg-node/egpt_wasm_bg.wasm": "e204498ac7439c294653476c0d214345713e6170819281b27bbbcba217a5a5e9",
8
8
  "fat/pkg-node/package.json": "a0c04cd40b60be10e9ddd6e1d4763fd578808321d663c6d4201530e2a4ebdba4",
@@ -38,15 +38,15 @@
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": "d279fa343782acda0547922ac485f623669c193148c3fb2c31df0b96570f3cfd",
41
+ "frqtl/objects/FrqtlApp.js": "0537a6e6b71fc763e92404952263d3bef4dbf273c86d45c5609ee72e69a30a56",
42
42
  "frqtl/objects/ObjectFactory.js": "a8f050ce61b7ab9c9b4f81e9eeaebf05c3cc0b532c741ce93d6e952489772b11",
43
- "frqtl/objects/RecipeContext.js": "ca21e08180570b587a4675bbeee1cf626f7b6cf71ba4d4d1225a805d6856865e",
43
+ "frqtl/objects/RecipeContext.js": "b21ca03f799a28848b190834274c061f6dde7fa7f3d17e5eac015550db90d8d7",
44
44
  "frqtl/objects/collectors/DataCollector.js": "8736d6cb77eec36ce2adcf2bc3b0d2436eeb70a73b77182ff053efdc6ca071df",
45
45
  "frqtl/objects/constants/WallBehaviors.js": "8a9cbdb96eadf84f1d61d215f6921124ec5dbf9e2938c09894a0aa083ddcaea0",
46
46
  "frqtl/objects/geometry/SlabFace.js": "847921b124d3bc1692e88c363c087d392a1baa7c652a3a61e381d0f8a8342e57",
47
47
  "frqtl/objects/instruments/capture-host.js": "ba5001afa2d8f4e438c5eacb2a5456d903401de8584d170c0b02ff3014794edd",
48
48
  "frqtl/objects/instruments/capture.js": "53a746cb51e1c83326eebaa98083861c7ee593f2bcb2317473446a1a02b59b5c",
49
- "frqtl/objects/instruments/hosted-eyes.js": "b6de888ea2752045feacd08333074dc01870fa20cba04ef48ae721215d9880db",
49
+ "frqtl/objects/instruments/hosted-eyes.js": "0d70708546f2e5917deb0a5638a535d7822f2141d69595db406cb0e074225883",
50
50
  "frqtl/objects/kinds-entry.js": "a7818357864dc900bd93350635016f938c3dca8dcb665a62df802ba5cd0b3c93",
51
51
  "frqtl/objects/kinds.js": "107b166c96c93fd517f173fdbe7006d6f0b2694d5f799f7ec9d4f695313f1832",
52
52
  "frqtl/objects/objects/Box.js": "0c08fa4753b78ef56d07aae3393a6268647b550ead58e409cff13eabc8b34b5d",
@@ -73,6 +73,8 @@
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": "7dce4e1ac5b5ab842fac9f6c9d754322b3696afb7b774e8c2ac018f803a780a6",
77
+ "frqtl/objects/record/open-record.js": "2da2abcccd3ad5bc39eb7fcfb764601cd031dd0afa9b3c9cb96b0669621827df",
76
78
  "frqtl/objects/schema/engine-scopes.js": "1777dd3d2443df7c7d7fbcd25974bcb758606bdf3efa5c21408009167d00e3e6",
77
79
  "frqtl/objects/schema/index.js": "74ba0b6a6e057cfd40edf2ce41fcce7206c0204e90eeee4a8db3b711f10ffd33",
78
80
  "frqtl/objects/schema/scopes.js": "3b9c2d4f20604d6384ec625ab5e4a38085fa9696c5e443ff66b45a8f2330c5f6",
@@ -110,7 +112,7 @@
110
112
  "frqtl/parts/DetectorWall.js": "49d9d9526b5e36180122617dd84b16e44624eee76fb2d547868e8cd41eeacbe0",
111
113
  "frqtl/parts/FrqtlControlOverlay.js": "f862b1529265224aa96878b56b0c1c6e65683ba3fd6b517df86e17e92147f9c4",
112
114
  "frqtl/parts/FrqtlControlsPanel.js": "792d03dea218bdc335ba7e04362be620edbfa331bed9de087d61942299728cc1",
113
- "frqtl/parts/FrqtlVision.js": "d4f913f92810bf33a989858616b7dee776e01edd48fbaf4b31f0606fb85ec0fd",
115
+ "frqtl/parts/FrqtlVision.js": "118d0874249fa55783b196fd01297b3347951267a4add93381433a72c46e8622",
114
116
  "frqtl/parts/GraphingDetectorWall.js": "6bff06f951459a3cd327c2eb235d159520933762be0343e1925d153fc2fe6423",
115
117
  "frqtl/parts/LargeObject.js": "637ea4c4b5052b916dace520cbb6772f1f8f2e367764ea399dab2a74fff4ed65",
116
118
  "frqtl/parts/LightColor.js": "8637e5a09bbad3a4fdd2034ccb44dfc1fda743e29b12b4c685a64b9e1df28daa",
@@ -155,10 +157,8 @@
155
157
  "frqtl/scenes/sun.js": "fa06cac429af5a2ad4fe846969a7bf763f2a76d9d9b169328cd1f45bad6bda8f",
156
158
  "frqtl/scenes/two-source.js": "1444a0e503f4a5e349d4364e31e698bc67d723a4b2864f262603e75ba5f9f088",
157
159
  "frqtl/scenes/wave.js": "cabbece84daceca9199bbea22be610281bdf3137c8c5724bc601758e711f111f",
158
- "shim/bundle/frqnb.js": "a237a81eb6074412d9a21f9dd7db96c80e1a4bc4c5f2a4b22f5ae556a37f6d5e",
159
- "shim/bundle/index.js": "6f82d9636233a6008e6d72d75df83aaef881ea711625eb24bc8841c6a2597376",
160
- "shim/bundle/indexHtml.js": "6d3ce242c4f139b542981164fe2b1d8b1c37d9423904bc1299531f594ce4ccc6",
161
- "shim/bundle/manifest.js": "796e25411d4f1d335277e954cf245352f95f324a1403062caa430142b9e8748c",
160
+ "shim/bundle/index.js": "ea7891e417a929e2b90dc8337750d247a355f84f7f581cf361d4bc8f473d42b5",
161
+ "shim/bundle/indexHtml.js": "2c4fbc502bb33fce93596cee19e143a58bb63c18d0b18ea597f81610e3a9a027",
162
162
  "shim/bundle/zip.js": "00f7b5fb5f36ff40a59d86758c503d9d7fb43949c1b5a4f2e7367c43d92786f9",
163
163
  "shim/bus.js": "dac2eeccd8774268c51014be4c46c00b9985fdf49ebb44355aa370e949c8c5bc",
164
164
  "shim/controls/index.js": "2aaf1f14b104bba8b912d68fe99068a8c544770b9eac43cdf6d992c311ec8f69",
@@ -166,15 +166,15 @@
166
166
  "shim/engine/artifact-shell.js": "b4c1d8bf65403caea9357fecd91172a1689baed8aad89433160d595881f16010",
167
167
  "shim/engine/boot-phases.js": "c6e12e12824bcf6f1a289f46e41c898a5416c80c131594525145aae9a3eefbfa",
168
168
  "shim/engine/engine-stack-boot.js": "0abf57dcff2ca2bb025c787a17875d49d54d9218ac08c78cb9c678dbb6118d08",
169
- "shim/engine/router.js": "32e2c1906459018f331b089253b6f280512da965fcf93753796fd9763ea9c13c",
169
+ "shim/engine/router.js": "a4ec678ab0e38381f761e40e8fbcb34dfea76d96df31a634f3d9f0b57d743570",
170
170
  "shim/experiment-frame/index.js": "5a0219aaa225d8f808c405b08623ce85ada37d83fb4f55f983db3303e431e47f",
171
171
  "shim/expr/ast-schema.js": "4f1133b887639bbd86fab976292323bf4a955cdc873ff7ca8e8a8eb3b416923f",
172
172
  "shim/field-descriptor/index.js": "6647d57d1523519114adc0a5cb66335ec5121c39ab294b9c97bdfb9e5d7f61e0",
173
173
  "shim/frame-data/index.js": "df6957b0b4a8e0ee0035bc970647b90089b421ae7f05911cc047d48457164be6",
174
- "shim/index.js": "d1f7d9a0a1fea320f641e2f9ec36174f243f03aca1a84804d74b33363703c8dc",
174
+ "shim/index.js": "141c68dbce88643815415645d5f896d55770208866e5b88b07b228d6a7d117e2",
175
175
  "shim/notebook/canvas-guard.js": "b19d3a86df3835bd617c7130cde997b6f8a8a0c140f95338b733115d5947ed7b",
176
176
  "shim/notebook/catalog.js": "68243a154e12bf36a76598aa554119f6e3b8b5363c8452d157c8a562b9c38dc1",
177
- "shim/notebook/format.js": "a552b161792873252a2163cd382bec2eb86651bcbdf96bd449d1b0007a0f2560",
177
+ "shim/notebook/format.js": "f8b1da9eb37da3a9609f7721426e2242f1279780ea212ea08d9f3c50bb18b578",
178
178
  "shim/notebook/frqtl-setups-importmap.js": "df86defa90cb85675564e7bb269451684b5cc7679f2c6b1884c07c8272e8ff1c",
179
179
  "shim/notebook/kernel.js": "65cf7e4ba12b24521143997a38cb0e7d0a8aa63f9f46868494eff7a41a887c7b",
180
180
  "shim/notebook/knob.js": "f07e9ac9697c7178aa68e4b2f4300145f8e2d2dc41b64aa0afede7e2d68705b1",
@@ -184,7 +184,7 @@
184
184
  "shim/notebook/realm-registry.js": "ffee52e7c93447c8b82bfc0784b51346686b4373766da298f50a759d3443ab6a",
185
185
  "shim/notebook/realm-transport.js": "5173676a94211f85122e0581a16abba925e9c4b5900b5891911bfaec5ea02daf",
186
186
  "shim/notebook/setup-builder.js": "abf8a7fed017776634d5ae3b595de7f00b32779590319cd48d95a6d9d0523a96",
187
- "shim/notebook/store.js": "ff73cd6f2d9ffa4dc434f15edbf69c5c62360b0ced6a023fd6279f6d95ae895e",
187
+ "shim/notebook/store.js": "fe34d31907e7a8f303dfcc35b9c0439faf2c32fbc5c0bb4f899692c25bef9a4d",
188
188
  "shim/notebook/vendor-importmap.js": "0c6578567980225593690e06fb4d32f0b50c47fe87a7467741a03846f6da68ed",
189
189
  "shim/notebook/view-builder.js": "1961962be16ef526934c43193828473447f2ec9ac0144f39bc5108c327d9069a",
190
190
  "shim/notebook/view-ctx.js": "6287aee366104bece827eecccf675540571d6db2b6833bff837be49eb60ce111",
@@ -193,19 +193,21 @@
193
193
  "shim/package.json": "609158e6c5fbc237939fa3ddf7faab80ab690bdc0c8d584414a885130103c4e8",
194
194
  "shim/page-params/index.js": "f372a85af163a492425cee2584513fa631a642cb805fa283ad122d445ebec4b3",
195
195
  "shim/project/identity.js": "5a0dbd07bfad5c5aa6a529994a604546dbf3e6978b9529ca97ce2c57aa8e563f",
196
- "shim/project/index.js": "11500ad4cc28e25b3d5ec500e3629dae3f794b640121164d15040d3d9b439244",
197
- "shim/project/link.js": "171d6c3522f7af821a1bd373a6ad9afc47402bd21cd1121625b00a2cabb86324",
196
+ "shim/project/index.js": "81d1a5d84c8bd5f31de8d9849d385509a73582e2ec6db4f779b018fe1b25404f",
197
+ "shim/project/link.js": "1774cde446c3217afb84e19fe44205d620764c863bff89e348a0645aed451ca2",
198
+ "shim/project/manifest.js": "442267af07ae1b977ff6aeaca49d98ce82b77f548cfdd1ed0f258b3429d75f31",
198
199
  "shim/project/node-specifier-hook.mjs": "8601edf762bd9cd011632bc681d0589dfc77368cda0e994125d0eb123fea89d0",
199
200
  "shim/project/parts-module.js": "b08e0149f086640692e1b2ff8e0d7ae0379c2bc934a0891418de5c2b8c0e8e60",
200
- "shim/project/parts-writer.js": "a0db8296f19560808aa71ea74c9d3db5cee48df82a962b102288c69690f1d4c8",
201
+ "shim/project/parts-writer.js": "c9e9be475299ba24a81190d19260119816b9773135491a5024b55c40dd92f859",
202
+ "shim/project/record-format.js": "b680c3752f20535c08be832c6808fae000c2d2325f8caee57f98351043d091be",
201
203
  "shim/project/register-node.mjs": "9b408da7dc1b11d16237cd62caca281c823ea80173d386ad35e41ea17c34857f",
202
- "shim/project/specifiers.js": "af8a6b3d0c6c18c3050630d843b2d394870efcc6ac03f4f01bc612d48ae2c63d",
204
+ "shim/project/specifiers.js": "f73a41a7ffbd16063be314973d67e5261607b56b7ef7ee7927d9eaa0d541f3cc",
203
205
  "shim/registry/copy-gate.js": "cfbf52669c849b8837b326f2743458f1ead0720cad51a6c6d409c9bd6c7f9495",
204
206
  "shim/registry/exhibits.js": "98736b6b0dd82b5c1436da28fe3bf8d2d3ee840af8f5256d00c23e296c3bca9a",
205
207
  "shim/registry/experiments.js": "1d1484f144ade35ddcb94b23712df539b3b432a45130e159a6f47146de188165",
206
- "shim/registry/index.js": "2a8e27dc83b5f7eb74b747b511ebc413a0de0dd43e27535296f504535353e2cd",
207
- "shim/registry/scene-source.js": "1e02af7af7ee0d879554f9dc12b9aba446d7966a3ea6aa6b4b2e8b460041ab01",
208
- "shim/registry/walkthrough.js": "08726aec2a68d45c36f8919f43c8e3fa255c325b0d63c9001b54d8ba07c04521",
208
+ "shim/registry/index.js": "a3cef8c227d06ccc55735fe2989fbd07b0cf2f323d11b5bed23092fc967e88fc",
209
+ "shim/registry/scene-source.js": "0e873f9afa8175a318db14f0761d9379687024b1d49f69f2a0e3a22285d1029b",
210
+ "shim/registry/walkthrough.js": "65d74822585d271f4d74f0821fc158698bc39d7427b9bca3a61584c3c6d1a4a5",
209
211
  "shim/render-state/apply.js": "fd0830ee4bb2668ac5b3254c29180bce9524d2ffb11bf6124e834ab8674a757a",
210
212
  "shim/render-state/index.js": "9015405ef2ea3d88bf004a0aa3bea04eb6bdb86270d68ef5622538a9bae5fa6d",
211
213
  "shim/render/capture-envelope.js": "2667a355169bb124fd8117e9cbf1d630085257b9f6fa537362f12d0aa649da2d",
@@ -215,10 +217,10 @@
215
217
  "shim/rpc/index.js": "56af3afeed22e8d718cd2f7a8ad70db8745f54dbc139af9d71f1d73e2ca37698",
216
218
  "shim/runtime/index.js": "cd728294c4693de3059f30f272f267b88b39c19a451edd30c75012e1bab755f5",
217
219
  "shim/storage/boot.js": "689504e15dc075e32d08abf61c75a45b1d565c09f086b42400dae40d8186c411",
218
- "shim/storage/index.js": "9f10a2ac220e7066493c3da0cea125ab5afb1e3d27d71fbef349f99caaeaa8b9",
220
+ "shim/storage/codec-chains.js": "6eab30c3ab7d8bd26059f386f91f9e43a87a6a20980d36bf7271eb72b6d109c0",
221
+ "shim/storage/folders.js": "9d69b6fcbdc8ae6da561acd42946933ac2d453d5d0659fcaf1cfba410a47a45a",
222
+ "shim/storage/index.js": "0939d88bcdb0cde5b564e1fc1b03144363c9747e917e706c4cc1f55fe4ee1ace",
219
223
  "shim/storage/local-storage-provider.js": "e14894e5d33bce961e66a2816e4b5c23b8e00534398ca9635d19de117f0f150e",
220
- "shim/storage/make-namespace-store.js": "da15fe99b19e25bd6e7f1c9e8a8852b15c43627fc0f9e2eb909503736f850a4a",
221
- "shim/storage/modules.js": "ceee32e0ea101ae49e5b0ab63007972a3f4127e9d81bbd2cf24cdb47d491623b",
222
- "shim/storage/projects.js": "1c7398dedbb7a67c80acbfe0dfb08ba4f069db9a7d946266c4636212834bd671"
224
+ "shim/storage/make-namespace-store.js": "f88cfc65244cd7a3c3a43c173001855bb95571916a117b22f38d051823f2856f"
223
225
  }
224
226
  }
@@ -46,12 +46,15 @@
46
46
  * eyes.saveStill(still, 'out.png'); // …and the picture, in one line
47
47
  * } finally { await eyes.dispose(); }
48
48
  *
49
+ * The Record door rides the handle (`eyes.record.*`, `eyes.act.*`); `writeRecordFolder(folder, dir)` is the ONE node
50
+ * writer of the folder `eyes.record.stop()` returns (hosted-eyes.js).
51
+ *
49
52
  * `FrqtlVision` is re-exported for the encoder itself (`FrqtlVision.toPNG(still)` → PNG bytes) — the
50
53
  * node half of the one-encoder-per-host pair; it is the parts barrel's own instance.
51
54
  */
52
55
 
53
56
  import { createRequire } from 'node:module';
54
- import { FrqtlHostedEyes as HostedEyes } from '../frqtl/objects/instruments/hosted-eyes.js';
57
+ import { FrqtlHostedEyes as HostedEyes, writeRecordFolder } from '../frqtl/objects/instruments/hosted-eyes.js';
55
58
  import { FrqtlVision } from '../frqtl/parts/index.js';
56
59
 
57
60
  // The WRONG-HOST DOOR: `./eyes` DRIVES a browser from node; loaded IN a page it can only fail, so the
@@ -106,4 +109,4 @@ export function openHostedEyes(opts) {
106
109
  return HostedEyes.open(bag);
107
110
  }
108
111
 
109
- export { FrqtlVision };
112
+ export { FrqtlVision, writeRecordFolder };
@@ -954,6 +954,11 @@ import { paintBootFailure } from '../experiments/_shared/boot-failure.js';
954
954
  x.inParts = true;
955
955
  };
956
956
 
957
+ /** beginWorld() — a (re-)inited world has not stepped: its last TickData is gone (worldTick reads 0, repaint draws nothing). */
958
+ FrqtlApp.prototype.beginWorld = function () {
959
+ this._lastTickData = null;
960
+ };
961
+
957
962
  /** clearDeclared() — empty the registry. Called where the world re-inits (FrqtlApp.setup, and the
958
963
  * RecipeContext co-init exp.createCanvas / exp.createScene): a fresh world has no declared objects. */
959
964
  FrqtlApp.prototype.clearDeclared = function () {
@@ -2296,7 +2301,7 @@ import { paintBootFailure } from '../experiments/_shared/boot-failure.js';
2296
2301
  this.stop();
2297
2302
  var self = this;
2298
2303
  function resume(result) {
2299
- self._lastTickData = null;
2304
+ self.beginWorld();
2300
2305
  // Always resume: "restart" means run the scene again from tick 0 — a restart
2301
2306
  // that left the loop paused would read as a dead button.
2302
2307
  self.start();
@@ -2572,6 +2577,28 @@ import { paintBootFailure } from '../experiments/_shared/boot-failure.js';
2572
2577
  return this._tickSuspend;
2573
2578
  };
2574
2579
 
2580
+ /**
2581
+ * runner.worldTick — READ-ONLY: the tick of THIS world: 0 before its first step, else the last delivered tick.
2582
+ * The 0 is a stated fact, not a default: `_lastTickData` is null until the world's first step, and beginWorld()
2583
+ * (the co-init and restart()) is the one writer of that null. It does NOT read `universe.tick`: the compat wrapper
2584
+ * keeps that field across a re-init until the next doTick, so after a restart it still answers the previous
2585
+ * world's tick. Every runner holds a world (its universe is built at construction), whether a sketch built it
2586
+ * through the replay door or a page built it by hand, so this always answers. THROWS when a delivered tick is not
2587
+ * a whole number ≥ 0.
2588
+ */
2589
+ Object.defineProperty(FrqtlApp.prototype, 'worldTick', {
2590
+ configurable: true,
2591
+ get: function () {
2592
+ if (this._lastTickData === null) { return 0; }
2593
+ var t = this._lastTickData && this._lastTickData.universeData && this._lastTickData.universeData.tick;
2594
+ if (!(typeof t === 'number' && t >= 0 && t % 1 === 0)) {
2595
+ throw new Error('[FrqtlApp.worldTick] the last delivered TickData carries tick ' + String(t) +
2596
+ ', not a whole number ≥ 0. (FAIL-LOUD.)');
2597
+ }
2598
+ return t;
2599
+ }
2600
+ });
2601
+
2575
2602
  /** @returns {boolean} whether the tick clock is currently held. */
2576
2603
  Object.defineProperty(FrqtlApp.prototype, 'ticksSuspended', {
2577
2604
  configurable: true,
@@ -75,6 +75,8 @@ import { kindOf } from './kinds.js';
75
75
  import { linkProject } from '../../shim/project/link.js';
76
76
  import { patchParts, PARTS_EXPORT, entryKeys, entryLiteral } from '../../shim/project/parts-writer.js';
77
77
  import { moduleOfKind } from '../../shim/project/parts-module.js';
78
+ import { MANIFEST_FILE } from '../../shim/project/manifest.js';
79
+ import { Record, openRecordOf } from './record/Record.js';
78
80
 
79
81
  /** The world-init contract (objects/setup/_worldInit.js). */
80
82
  function _worldInit() {
@@ -123,6 +125,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
123
125
  this._parts = new Map(); // declared object → { key, baseline } — the objects in `parts`
124
126
  this._code = new Map(); // declared object → baseline — the code declarations at build end
125
127
  this._declaredOnce = false;
128
+ this._record = null; // the open Record (exp.startRecord), or null
129
+ this._reauthored = null; // the Record the last re-authoring closed (startRecord checks its close was heard)
126
130
  }
127
131
 
128
132
  function _requireWorld(self) {
@@ -195,6 +199,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
195
199
  }
196
200
  // A (re-)inited world has no declared objects; the recipe re-declares what it builds.
197
201
  self._runner.clearDeclared();
202
+ self._runner.beginWorld(); // a (re-)inited world has not stepped
198
203
  W.applyPreInit(cfg, u); // pre-init world keys (iframeIntervalSeconds) — the engine reads them at init
199
204
  u.init.apply(u, args);
200
205
  self._world = Object.freeze({ renderer: renderer, w: w, h: h, d: renderer === 'three' ? d : null,
@@ -300,7 +305,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
300
305
  }
301
306
  var obj = self.addObject(Kind, Object.assign({ name: key }, _plain(props)));
302
307
  self._runner._adoptIntoParts(obj);
303
- self._parts.set(obj, { key: key, baseline: _capture(self, obj) });
308
+ self._parts.set(obj, { key: key, baseline: _capture(self, obj), kind: self._runner.kindOf(obj) });
304
309
  out[key] = obj;
305
310
  });
306
311
  return Object.freeze(out);
@@ -366,6 +371,22 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
366
371
  return props;
367
372
  }
368
373
 
374
+ /**
375
+ * Spell a touched `center` in the entry's OWN arity: two numbers when the entry
376
+ * spells two (a literal, or an array expression — entryLiteral's `length`) and the live z is 0; otherwise the
377
+ * live [x, y, z] (an entry spelled three, a 3D hand off z 0, an entry whose center is not an array). The
378
+ * read-back (_verifyWritten) guards every case. Mutates and returns `props`.
379
+ */
380
+ function _spellCenter(text, name, props) {
381
+ var v = props.center;
382
+ if (!Array.isArray(v) || v.length !== 3 || v[2] !== 0) { return props; }
383
+ if (entryKeys(text, name).indexOf('center') === -1) { return props; }
384
+ var r = entryLiteral(text, name, 'center');
385
+ var n = r.literal ? (Array.isArray(r.value) ? r.value.length : null) : r.length;
386
+ if (n === 2) { props.center = [v[0], v[1]]; }
387
+ return props;
388
+ }
389
+
369
390
  /**
370
391
  * The post-patch check: what the patched text now declares for entry `name`, read back (entryLiteral) for every
371
392
  * written key and folded with the baseline for every unwritten one, must equal the live capture — `rect` in its
@@ -379,6 +400,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
379
400
  Object.keys(written).forEach(function (k) {
380
401
  var r = entryLiteral(text, name, k);
381
402
  decl[k] = r.literal ? r.value : undefined;
403
+ // A two-number center declares z 0 (the reading placeDeclaredAt gives an omitted z).
404
+ if (k === 'center' && Array.isArray(decl[k]) && decl[k].length === 2) { decl[k] = decl[k].concat([0]); }
382
405
  });
383
406
  function refuse(k, declared, liveV) {
384
407
  refused.push({ op: 'set', name: name, key: k, reason: 'the written text declares ' + _JSON(declared) + ' but the live object is ' +
@@ -436,9 +459,10 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
436
459
  * changes patchParts' changes (a `literalized` set names the expression it replaced)
437
460
  * refused every hand not written, { op, name, key?, reason }
438
461
  */
439
- RecipeContext.prototype.toSketch = async function () {
462
+ RecipeContext.prototype.toSketch = async function (opts) {
440
463
  _requireWorld(this);
441
464
  _requireHeld(this, 'toSketch');
465
+ var by = this._record ? Record.requireBy(opts && opts.by, 'RecipeContext.toSketch') : null;
442
466
  var self = this;
443
467
  var runner = this._runner;
444
468
  var declared = runner.declared;
@@ -447,7 +471,7 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
447
471
  var adopted = [];
448
472
  var hands = runner._takeHandTouched();
449
473
  this._parts.forEach(function (row, obj) {
450
- if (declared.indexOf(obj) === -1) { edits.push({ op: 'remove', name: row.key }); return; }
474
+ if (declared.indexOf(obj) === -1) { edits.push({ op: 'remove', name: row.key, kind: row.kind, baseline: row.baseline }); return; }
451
475
  if (!hands.has(obj)) { return; }
452
476
  var kind = runner.kindOf(obj);
453
477
  var t = _touched(kind, row.baseline, _capture(self, obj));
@@ -459,8 +483,10 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
459
483
  refused.push({ op: 'set', name: row.key, key: k, reason: 'the live object no longer reports "' + k + '" — there is no value to write.' });
460
484
  });
461
485
  if (Object.keys(t.props).length) {
486
+ var raw = _plain(t.props); // the capture form, before the entry's spelling (the Record's row values)
462
487
  _spell(self._project.files[self._project.entry], row.key, kind, t.props, row.baseline);
463
- if (Object.keys(t.props).length) { edits.push({ op: 'set', name: row.key, props: t.props, obj: obj, kind: kind, baseline: row.baseline }); }
488
+ _spellCenter(self._project.files[self._project.entry], row.key, t.props);
489
+ if (Object.keys(t.props).length) { edits.push({ op: 'set', name: row.key, props: t.props, obj: obj, kind: kind, baseline: row.baseline, raw: raw }); }
464
490
  }
465
491
  });
466
492
  this._code.forEach(function (baseline, obj) {
@@ -487,7 +513,9 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
487
513
  });
488
514
  var entry = this._project.entry;
489
515
  var r = patchParts(this._project.files[entry], edits.map(function (e) {
490
- return e.op === 'set' ? { op: 'set', name: e.name, props: e.props } : e;
516
+ if (e.op === 'set') { return { op: 'set', name: e.name, props: e.props }; }
517
+ if (e.op === 'remove') { return { op: 'remove', name: e.name }; }
518
+ return { op: 'add', name: e.name, Kind: e.Kind, module: e.module, props: e.props };
491
519
  }), { entry: entry });
492
520
  // Never decide it succeeded without asking: read the patched text back against each moved object.
493
521
  edits.forEach(function (e) {
@@ -511,11 +539,76 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
511
539
  var key = obj.name || runner.kindOf(obj);
512
540
  if (refusedAdd[key]) { return; }
513
541
  runner._adoptIntoParts(obj);
514
- self._parts.set(obj, { key: key, baseline: _capture(self, obj) });
542
+ self._parts.set(obj, { key: key, baseline: _capture(self, obj), kind: runner.kindOf(obj) });
515
543
  });
516
- return { files: Object.assign({}, files), changes: r.changes, refused: refused.concat(r.refused) };
544
+ var allRefused = refused.concat(r.refused);
545
+ if (this._record) { _recordRows(this, edits, allRefused, by); }
546
+ return { files: Object.assign({}, files), changes: r.changes, refused: allRefused };
517
547
  };
518
548
 
549
+ /**
550
+ * The Record's rows for one toSketch, from the writer's own edits as WRITTEN (a refused edit, or a refused key
551
+ * of one, makes no row), in edit order (R3). A `set` whose touched keys include the part's position authority
552
+ * (runner._placementAuthority: `center`, or `rect` with its derivedFrom reads) is a `move` with that ONE key in
553
+ * its capture form; its other touched keys are one `setDeclared` row. `add` → [kind, props as written];
554
+ * `remove` → [kind, the baseline's writable props].
555
+ */
556
+ function _recordRows(self, edits, refused, by) {
557
+ var runner = self._runner;
558
+ var rec = self._record;
559
+ function refusedKeys(name, op) {
560
+ var keys = {};
561
+ var all = false;
562
+ refused.forEach(function (x) {
563
+ if (x.name !== name || x.op !== op) { return; }
564
+ if (x.key === undefined) { all = true; } else { keys[x.key] = true; }
565
+ });
566
+ return all ? null : keys;
567
+ }
568
+ function writableProps(kind, bag) {
569
+ var w = _writable(kind);
570
+ var out = {};
571
+ Object.keys(bag).forEach(function (k) { if (w[k] && k !== 'name') { out[k] = _plain(bag[k]); } });
572
+ return out;
573
+ }
574
+ edits.forEach(function (e) {
575
+ if (e.op === 'add') {
576
+ if (refusedKeys(e.name, 'add') === null) { return; }
577
+ rec._row({ name: e.name, op: 'add', by: by, from: null, to: [e.Kind, _plain(e.props)] });
578
+ return;
579
+ }
580
+ if (e.op === 'remove') {
581
+ if (refusedKeys(e.name, 'remove') === null) { return; }
582
+ rec._row({ name: e.name, op: 'remove', by: by, from: [e.kind, writableProps(e.kind, e.baseline)], to: null });
583
+ return;
584
+ }
585
+ var bad = refusedKeys(e.name, 'set');
586
+ if (bad === null) { return; }
587
+ var raw = e.raw;
588
+ var auth = (raw.center !== undefined || raw.rect !== undefined) ? runner._placementAuthority(e.obj) : null;
589
+ var authReads = {};
590
+ if (auth) {
591
+ authReads[auth] = true;
592
+ ObjectSchema.derivedReads(e.kind).forEach(function (d) { if (d.field === auth) { authReads[d.name] = true; } });
593
+ }
594
+ var moveRefused = Object.keys(bad).some(function (k) { return authReads[k]; });
595
+ if (auth && raw[auth] !== undefined && !moveRefused) {
596
+ var from = {}, to = {};
597
+ from[auth] = _plain(e.baseline[auth]);
598
+ to[auth] = _plain(raw[auth]);
599
+ rec._row({ name: e.name, op: 'move', by: by, from: from, to: to });
600
+ }
601
+ var pf = {}, pt = {}, any = false;
602
+ Object.keys(raw).forEach(function (k) {
603
+ if (k === auth || bad[k]) { return; }
604
+ pf[k] = e.baseline[k] === undefined ? null : _plain(e.baseline[k]);
605
+ pt[k] = _plain(raw[k]);
606
+ any = true;
607
+ });
608
+ if (any) { rec._row({ name: e.name, op: 'setDeclared', by: by, from: pf, to: pt }); }
609
+ });
610
+ }
611
+
519
612
  /** The names of declared objects a hand marked whose live capture differs from their baseline (not yet written). */
520
613
  function _unwrittenHands(self) {
521
614
  var runner = self._runner;
@@ -553,11 +646,14 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
553
646
  * setOrbit) are kept; `kept` lists 'cameraPose' only when a pose was captured and re-applied. A `rect` /
554
647
  * `center` patch sets where the entry STARTS. The loop's run state is kept. REFUSES (throws) while a hand
555
648
  * made since the last toSketch is unwritten (write it first: await exp.toSketch()), and a `name` patch (the
556
- * key is the name).
649
+ * key is the name). `from` and `to` are the patched keys' capture (ObjectSchema.captureProps) before and after the
650
+ * re-run — one form on both sides (a z-slab wall's rect is 6 numbers even when the patch typed 4); the patch as
651
+ * typed is in `files`. With a Record open, its `setDeclared` row carries the same `from`/`to`, written after the re-run.
557
652
  */
558
- RecipeContext.prototype.setDeclared = async function (name, patch) {
653
+ RecipeContext.prototype.setDeclared = async function (name, patch, opts) {
559
654
  _requireWorld(this);
560
655
  _requireHeld(this, 'setDeclared');
656
+ var by = this._record ? Record.requireBy(opts && opts.by, 'RecipeContext.setDeclared') : null;
561
657
  var runner = this._runner;
562
658
  var obj = null;
563
659
  this._parts.forEach(function (row, o) { if (row.key === name) { obj = o; } });
@@ -615,7 +711,17 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
615
711
  runner.cameraControl.setOrbit(pose);
616
712
  kept.push('cameraPose');
617
713
  }
618
- return { set: name, from: from, to: _plain(patch), tick: 0, kept: kept, files: Object.assign({}, files) };
714
+ // `to` is what the re-run declared, in the capture form `from` is in (the patch as typed is in the text).
715
+ var rebuilt = null;
716
+ this._parts.forEach(function (row, o) { if (row.key === name) { rebuilt = o; } });
717
+ if (!rebuilt) {
718
+ throw new Error('[RecipeContext.setDeclared] the re-run declared no "' + name + '" — the patched text dropped the entry. (FAIL-LOUD.)');
719
+ }
720
+ var after = _capture(this, rebuilt);
721
+ var to = {};
722
+ Object.keys(patch).forEach(function (k) { to[k] = after[k] === undefined ? null : _plain(after[k]); });
723
+ if (this._record) { this._record._row({ name: name, op: 'setDeclared', by: by, from: _plain(from), to: _plain(to) }); }
724
+ return { set: name, from: from, to: to, tick: 0, kept: kept, files: Object.assign({}, files) };
619
725
  };
620
726
 
621
727
  RecipeContext.prototype.addTickFunction = function (fn) {
@@ -683,8 +789,8 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
683
789
  var linked = null;
684
790
  if (sketch && typeof sketch === 'object' && typeof sketch.buildExperiment === 'function') {
685
791
  if (!sketch.project || typeof sketch.project !== 'object' || !sketch.project.files || typeof sketch.project.entry !== 'string') {
686
- throw new TypeError('[RecipeContext.build] build(linked): linked is linkProject\'s result { buildExperiment, files, ' +
687
- 'urls, project } (lib/shim/project/link.js). (FAIL-LOUD.)');
792
+ throw new TypeError('[RecipeContext.build] build(linked): linked is linkProject\'s result (its contract: ' +
793
+ 'lib/shim/project/link.js). (FAIL-LOUD.)');
688
794
  }
689
795
  linked = sketch;
690
796
  sketch = linked.buildExperiment;
@@ -693,6 +799,12 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
693
799
  'linkProject\'s result (got ' + typeof sketch + '). (FAIL-LOUD.)');
694
800
  }
695
801
  if (typeof sketch === 'function') {
802
+ // ONE Record per booted world: a build of text that is not the held text re-authors it, and closes the
803
+ // open Record with the world before (a rebuild of the same text — restart, setDeclared's re-run — does not).
804
+ if (this._record && !(linked && this._project && _sameFiles(linked.project.files, this._project.files))) {
805
+ this._reauthored = this._record;
806
+ this._record._closeOnReauthor(this._project);
807
+ }
696
808
  this._buildFn = sketch;
697
809
  this._project = linked ? linked.project : null;
698
810
  this._urls = linked ? (linked.urls || null) : null;
@@ -719,6 +831,60 @@ import { moduleOfKind } from '../../shim/project/parts-module.js';
719
831
  return this;
720
832
  };
721
833
 
834
+ /** Two file maps hold the same paths with byte-equal text. */
835
+ function _sameFiles(a, b) {
836
+ var ka = Object.keys(a), kb = Object.keys(b);
837
+ if (ka.length !== kb.length) { return false; }
838
+ return ka.every(function (k) { return Object.prototype.hasOwnProperty.call(b, k) && a[k] === b[k]; });
839
+ }
840
+
841
+ /**
842
+ * exp.startRecord() → Promise<Record> — open a Record (lib/frqtl/objects/record/Record.js) on this facade's world.
843
+ * REFUSES (rejects): a second open Record (one per facade, one per runner); a held project with no frqtl.json
844
+ * (a Record snapshots a folder); a hand the sketch text does not hold yet (as setDeclared). While it is open,
845
+ * toSketch({ by }) and setDeclared(name, patch, { by }) name who acted.
846
+ */
847
+ RecipeContext.prototype.startRecord = async function () {
848
+ _requireWorld(this);
849
+ _requireHeld(this, 'startRecord');
850
+ if (this._record) {
851
+ throw new Error('[RecipeContext.startRecord] a Record is already open on this facade (one per booted world) — ' +
852
+ 'await rec.stop() first. (FAIL-LOUD.)');
853
+ }
854
+ // A re-authoring closed the previous Record; if that close was REFUSED and nobody read it, its rows would be lost
855
+ // without a word: refuse until the host reads `rec.closed` (the rejection carries err.record).
856
+ var last = this._reauthored;
857
+ if (last) {
858
+ await last._settled;
859
+ if (last._refused && !last._closedRead) {
860
+ var err = new Error('[RecipeContext.startRecord] the previous Record of this facade was closed by a re-authoring and ' +
861
+ 'REFUSED (' + last._refused.message + ') — nobody read rec.closed, so its ' + last._refused.record.changes.length +
862
+ ' row(s) would be lost: read rec.closed (its rejection carries err.record = { changes, clips }) first. (FAIL-LOUD.)');
863
+ err.record = last._refused.record;
864
+ throw err;
865
+ }
866
+ this._reauthored = null;
867
+ }
868
+ if (openRecordOf(this._runner)) {
869
+ throw new Error('[RecipeContext.startRecord] another facade holds an open Record on this runner. (FAIL-LOUD.)');
870
+ }
871
+ if (!Object.prototype.hasOwnProperty.call(this._project.files, MANIFEST_FILE)) {
872
+ throw new Error('[RecipeContext.startRecord] a Record snapshots a folder; boot it as one — the held project carries ' +
873
+ 'no ' + MANIFEST_FILE + ': add files[\'' + MANIFEST_FILE + '\'] = serializeManifest(manifestFor({ name, entry: SKETCH_ENTRY })) ' +
874
+ '(all from ./project) to the files you boot. (FAIL-LOUD.)');
875
+ }
876
+ var unwritten = _unwrittenHands(this);
877
+ if (unwritten.length) {
878
+ throw new Error('[RecipeContext.startRecord] "' + unwritten.join('", "') + '" carries a hand the sketch text does ' +
879
+ 'not hold yet — write the hand first: await exp.toSketch(). (FAIL-LOUD.)');
880
+ }
881
+ var rec = new Record(this);
882
+ this._record = rec;
883
+ try { await rec._ready(this._project); }
884
+ catch (e) { rec._detach(); throw e; }
885
+ return rec;
886
+ };
887
+
722
888
  RecipeContext.prototype.run = function (buildFn) {
723
889
  if (buildFn && typeof buildFn === 'function' && !this._built) { this.build(buildFn); }
724
890
  _requireWorld(this);