@descix/frqtl-node 0.6.1-p3.2 → 0.6.1-p3.4
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 +1 -1
- package/README.md +1 -1
- package/dist/dist-manifest.json +21 -21
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +301 -12
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +6 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +66 -17
- package/dist/frqtl/objects/FrqtlApp.js +17 -1
- package/dist/frqtl/objects/RecipeContext.js +17 -0
- package/dist/frqtl/objects/record/Record.js +4 -9
- package/dist/frqtl/parts/FrqtlVision.js +158 -27
- package/dist/frqtl/parts/LiveChart.js +1 -1
- package/dist/frqtl/parts/SphereEmitter.js +2 -2
- package/dist/frqtl/parts/Tunnel.js +1 -1
- package/dist/frqtl/parts/Wall.js +1 -1
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +33 -22
- package/dist/frqtl/scenes/mirror-route.js +10 -6
- package/dist/shim/engine/router.js +2 -2
- package/dist/shim/field-descriptor/index.js +5 -5
- package/dist/shim/host/device.js +6 -2
- package/dist/shim/registry/exhibits.js +10 -10
- package/dist/shim/registry/walkthrough.js +38 -90
- package/dist/shim/render/capture-envelope.js +21 -0
- package/dist/shim/render/sun-look.js +6 -11
- package/llms.txt +2 -2
- package/package.json +1 -1
package/dist/frqtl/parts/Wall.js
CHANGED
|
@@ -971,7 +971,7 @@ import { wallBehaviorV2ValuesMap as _wallBehaviorV2ValuesMap, bounceDirectionVal
|
|
|
971
971
|
'on. Zero physics of its own.',
|
|
972
972
|
// STUDIO UX presentation contract (A0 vocab, owned at lib/shim/field-descriptor).
|
|
973
973
|
// A wall has a genuine draggable/resizable extent (unlike the two-radii
|
|
974
|
-
// SphereEmitter) → resizable:true
|
|
974
|
+
// SphereEmitter) → resizable:true (resize zones are 2D; the 3D Hand only moves).
|
|
975
975
|
// shellRender:'run' — a wall is a boundary you SEE during Run (not a Design-only
|
|
976
976
|
// marker like an emitter), so its shell always paints.
|
|
977
977
|
ux: {
|
|
@@ -44,7 +44,6 @@
|
|
|
44
44
|
import { CameraControls, FrqtlControlOverlay, FrqtlControlsPanel, GraphingDetectorWall, SphereEmitter, Tunnel, Wall } from '@descix/frqtl-web/parts';
|
|
45
45
|
import * as ControlRegistry from '@descix/frqtl-web/shim/controls/registry.js';
|
|
46
46
|
import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
|
|
47
|
-
import * as SunLook from '@descix/frqtl-web/shim/render/sun-look.js';
|
|
48
47
|
|
|
49
48
|
const CW = 600, H = 400, WORLD_D = 600; // THE WORLD this scene states (its co-init call below)
|
|
50
49
|
// GEOMETRY and WAVELENGTH are two INDEPENDENT dials, and they are kept apart on
|
|
@@ -224,33 +223,45 @@ export default function buildExperiment(exp) {
|
|
|
224
223
|
collisionBehavior: 'destroy', name: 'zClipFar', visible: false });
|
|
225
224
|
exp.addObject('Wall', { rect: [0, 0, CW, H], z: -zHalf - 40, depth: 40,
|
|
226
225
|
collisionBehavior: 'destroy', name: 'zClipNear', visible: false });
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
226
|
+
// PACING (wall-clock only, never the ticks): 240 ticks/s, four times the 1:1 lock on a
|
|
227
|
+
// 60 Hz display, so the interference bands build up four times sooner in real time.
|
|
228
|
+
exp.runner.setRateLimits({ tpsLimit: 240 });
|
|
229
|
+
// THE SWITCH: this scene's god-ray look, computed from the light particles only (godraySource
|
|
230
|
+
// 'leaf') with the 'field' pass (each pixel marches along the local direction of travel), and the
|
|
231
|
+
// per-layer glow under it. The numbers are this scene's own, set by eye against a reference
|
|
232
|
+
// STUDIO picture (godrays 2.2 of [0, 3], godrayDensity 1.5 = the top of [0.2, 1.5],
|
|
233
|
+
// glowIntensity 0.66 of [0, 3]); the renderer declares the keys and their ranges and refuses any
|
|
234
|
+
// other. Default ON: the scene writes ON once at build, through the same function the knob calls.
|
|
235
|
+
// OFF restores EXACTLY what was live before the last ON (a host row, a STUDIO slider), through
|
|
236
|
+
// the same declared door. The knob shows its last written value (a render write has no
|
|
237
|
+
// read-back). The picture changes at the next drawn tick, so in a paused (Design) view it shows
|
|
238
|
+
// once the scene runs.
|
|
239
|
+
var RAYS_ON = { godrays: 2.2, godrayDensity: 1.5, godrayMode: 'field', godraySource: 'leaf', glowIntensity: 0.66 };
|
|
240
|
+
var RAYS_DEFAULT = 1;
|
|
241
|
+
var raysPrior = null; // the live values of RAYS_ON's keys captured at ON; written back at OFF
|
|
242
|
+
function setRays(v) {
|
|
243
|
+
if (v) {
|
|
244
|
+
// Capture only on the OFF -> ON edge: an ON pressed while already ON keeps the pre-ON state.
|
|
245
|
+
if (!raysPrior) {
|
|
246
|
+
raysPrior = {};
|
|
247
|
+
Object.keys(RAYS_ON).forEach(function (k) { raysPrior[k] = exp.runner.getRenderOption(k); });
|
|
248
|
+
}
|
|
249
|
+
exp.runner.setRenderOptions(RAYS_ON);
|
|
250
|
+
} else if (raysPrior) {
|
|
251
|
+
exp.runner.setRenderOptions(raysPrior);
|
|
252
|
+
raysPrior = null;
|
|
253
|
+
}
|
|
254
|
+
// OFF with nothing captured: nothing was written by this switch — no write.
|
|
255
|
+
}
|
|
236
256
|
var knobs = ControlRegistry.makeControlRegistry();
|
|
237
257
|
knobs.registerControls('dse-tunnel-rebuild', [
|
|
238
258
|
{ kind: 'scalar', id: 'godrays', label: 'god rays', channel: 'render', widget: 'toggle',
|
|
239
|
-
min: 0, max: 1, step: 1, value:
|
|
259
|
+
min: 0, max: 1, step: 1, value: RAYS_DEFAULT,
|
|
240
260
|
fmt: function (v) { return v ? 'on' : 'off'; },
|
|
241
|
-
onChange:
|
|
242
|
-
if (v) {
|
|
243
|
-
raysPrior = {};
|
|
244
|
-
Object.keys(RAYS_ON).forEach(function (k) { raysPrior[k] = exp.runner.getRenderOption(k); });
|
|
245
|
-
exp.runner.setRenderOptions(RAYS_ON);
|
|
246
|
-
} else if (raysPrior) {
|
|
247
|
-
exp.runner.setRenderOptions(raysPrior);
|
|
248
|
-
raysPrior = null;
|
|
249
|
-
}
|
|
250
|
-
// OFF with nothing captured: the control is at its initial OFF and has written nothing — no write.
|
|
251
|
-
},
|
|
261
|
+
onChange: setRays,
|
|
252
262
|
place: { x: 0.02, y: 0.04 } }
|
|
253
263
|
]);
|
|
264
|
+
setRays(RAYS_DEFAULT);
|
|
254
265
|
// Chrome (a page only): expose the knob to a host frame, and mount the overlay through the
|
|
255
266
|
// runner's ONE door (runner.controlHost) — before a canvas is mounted (a notebook pane) there is
|
|
256
267
|
// none, so the knob stays in the exposed registry only.
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
// (d) the population counter carries a z-slab covering the WHOLE universe z-band (the emitter's
|
|
33
33
|
// gateCounter names it, so it stays a code declaration);
|
|
34
34
|
// (e) MOVE/LOOK camera joysticks (the OMNI_3D pattern).
|
|
35
|
-
// The 3D mode's render intent —
|
|
36
|
-
// particles only (godraySource 'leaf'),
|
|
37
|
-
//
|
|
38
|
-
// lib/shim/
|
|
35
|
+
// The 3D mode's render intent — the particles hidden so the light field alone shows, strong
|
|
36
|
+
// 'field' god-rays computed from the light particles only (godraySource 'leaf'), a soft glow, and
|
|
37
|
+
// a face-on camera close enough that the ring spans the canvas height — rides the registry mode's
|
|
38
|
+
// renderState (lib/shim/registry/walkthrough.js), its one home.
|
|
39
39
|
|
|
40
40
|
import { CameraControls, SphereEmitter, Wall } from '@descix/frqtl-web/parts';
|
|
41
41
|
import * as FrqtlDisplay from '@descix/frqtl-web/compat/renderer/display-slot.js';
|
|
@@ -64,6 +64,9 @@ const vpos = function (v) { var a = (v * 36 + ROT) * D2R; return [CX + R * Math.
|
|
|
64
64
|
const pos = order.map(vpos);
|
|
65
65
|
const deg = function (a) { return ((a * 180 / Math.PI) % 360 + 360) % 360; };
|
|
66
66
|
const angOf = function (p, q) { return deg(Math.atan2(q[1] - p[1], q[0] - p[0])); };
|
|
67
|
+
// Where each mirror belongs: m1..m10 by name, at its star vertex (the centre of the slot in the ring). m4 and m6
|
|
68
|
+
// are declared off theirs below; STUDIO's "try it" reads this to know when a dragged mirror is home.
|
|
69
|
+
export const starPoints = Object.freeze(Object.fromEntries(pos.map(function (p, i) { return ['m' + (i + 1), Object.freeze(p.slice())]; })));
|
|
67
70
|
// Per-mirror IN-PLANE orientation from the in/out chords: ori = (in+out−180)/2, branch chosen
|
|
68
71
|
// so the engine reflect law out = 180 + 2·ori − in reproduces the outgoing chord exactly.
|
|
69
72
|
// Orientation is z-axis rotation, so the 3D slab depth adds NO new angle.
|
|
@@ -101,9 +104,10 @@ export const parts = {
|
|
|
101
104
|
m1: [Wall, { rect: [Math.round(pos[0][0] - SW / 2), Math.round(pos[0][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[0], color: PAL[0], hooks: ['recolor'] }],
|
|
102
105
|
m2: [Wall, { rect: [Math.round(pos[1][0] - SW / 2), Math.round(pos[1][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[1], color: PAL[1], hooks: ['recolor'] }],
|
|
103
106
|
m3: [Wall, { rect: [Math.round(pos[2][0] - SW / 2), Math.round(pos[2][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[2], color: PAL[2], hooks: ['recolor'] }],
|
|
104
|
-
m4
|
|
107
|
+
// m4 and m6 stand OFF their star vertices (m4 5 px right, m6 67 px toward the centre): the ring opens broken, so dragging one home is the thing to try.
|
|
108
|
+
m4: [Wall, { rect: [437, 103, SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[3], color: PAL[3], hooks: ['recolor'] }],
|
|
105
109
|
m5: [Wall, { rect: [Math.round(pos[4][0] - SW / 2), Math.round(pos[4][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[4], color: PAL[4], hooks: ['recolor'] }],
|
|
106
|
-
m6: [Wall, { rect: [
|
|
110
|
+
m6: [Wall, { rect: [215, 142, SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[5], color: PAL[5], hooks: ['recolor'] }],
|
|
107
111
|
m7: [Wall, { rect: [Math.round(pos[6][0] - SW / 2), Math.round(pos[6][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[6], color: PAL[6], hooks: ['recolor'] }],
|
|
108
112
|
m8: [Wall, { rect: [Math.round(pos[7][0] - SW / 2), Math.round(pos[7][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[7], color: PAL[7], hooks: ['recolor'] }],
|
|
109
113
|
m9: [Wall, { rect: [Math.round(pos[8][0] - SW / 2), Math.round(pos[8][1] - SH / 2), SW, SH], z: -Math.round(SD / 2), depth: SD, collisionBehavior: 'bounce', bounceDirection: 'reflect', orientationDeg: ORI[8], color: PAL[8], hooks: ['recolor'] }],
|
|
@@ -152,7 +152,7 @@ var DIST_PROFILE = buildDistProfile('../..');
|
|
|
152
152
|
// lib/shim/engine/test/router.test.mjs asserts them against the two
|
|
153
153
|
// package.json#version fields so a forgotten bump fails `make verify`, not a
|
|
154
154
|
// silent stale-CDN-URL bug.
|
|
155
|
-
var CDN_SDK_VERSION = '0.6.1-p3.
|
|
155
|
+
var CDN_SDK_VERSION = '0.6.1-p3.4'; // sdk/frqtl-web/package.json#version (the ONE version owner) — bound by router.test.mjs
|
|
156
156
|
var CDN_MATH_SDK_VERSION = '0.3.3-p2.1'; // sdk/egpt-math-sdk/package.json#version — bound by router.test.mjs; bumped with each npm publish
|
|
157
157
|
|
|
158
158
|
/**
|
|
@@ -526,7 +526,7 @@ var _assertProfileParity = (function () {
|
|
|
526
526
|
* // CDN mode (static hosting — the artifact host shell has no server, no page anchor):
|
|
527
527
|
* const cdnRouter = engineRouter({ mode: 'cdn', engine: 'wasm' });
|
|
528
528
|
* console.log(cdnRouter.resolve('compat/engine.js'));
|
|
529
|
-
* // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.1-p3.
|
|
529
|
+
* // 'https://cdn.jsdelivr.net/npm/@descix/frqtl-web@0.6.1-p3.4/dist/frqtl/compat/engine.js'
|
|
530
530
|
*
|
|
531
531
|
* // CDN mode, self-hosted: a copy of dist/ served from the consumer's own origin:
|
|
532
532
|
* const selfHosted = engineRouter({ mode: 'cdn', engine: 'wasm', base: 'https://example.org/vendor/frqtl-web/dist' });
|
|
@@ -199,8 +199,8 @@ var UX_HANDLE_ZONES = Object.freeze([
|
|
|
199
199
|
'scale-t', 'scale-b', 'scale-l', 'scale-r'
|
|
200
200
|
]);
|
|
201
201
|
|
|
202
|
-
// The
|
|
203
|
-
//
|
|
202
|
+
// The keys of a ux `draggable` block. Declared data only: STUDIO's Hand drag (2D and 3D) moves
|
|
203
|
+
// any declared object with a position authority and does not read it.
|
|
204
204
|
var UX_DRAGGABLE_MODES = Object.freeze(['2d']);
|
|
205
205
|
|
|
206
206
|
// When a manipulable object's visual shell renders (generalizes the
|
|
@@ -236,8 +236,8 @@ function _uxFail(msg) {
|
|
|
236
236
|
* resizable — boolean (default true). false ⇒ scale handles are never
|
|
237
237
|
* offered even for the SELECTED object (its corners never
|
|
238
238
|
* resize; the first-and-every interaction is select/move).
|
|
239
|
-
* draggable — { '2d':boolean } (default true).
|
|
240
|
-
*
|
|
239
|
+
* draggable — { '2d':boolean } (default true). Declared data only: no STUDIO
|
|
240
|
+
* surface reads it ('3d' is not a key).
|
|
241
241
|
* handles — string[] ⊆ UX_HANDLE_ZONES (default = ALL zones). The
|
|
242
242
|
* live interaction zones; a zone NOT listed is downgraded
|
|
243
243
|
* to 'move' by the overlay (never a resize on the selecting
|
|
@@ -273,7 +273,7 @@ function normalizeUx(partial) {
|
|
|
273
273
|
Object.keys(partial.draggable).forEach(function (k) {
|
|
274
274
|
if (UX_DRAGGABLE_MODES.indexOf(k) === -1) {
|
|
275
275
|
_uxFail('"draggable.' + k + '" is not a draggable mode ([' + UX_DRAGGABLE_MODES.join(', ') +
|
|
276
|
-
'])
|
|
276
|
+
']).');
|
|
277
277
|
}
|
|
278
278
|
});
|
|
279
279
|
var dr = { '2d': true };
|
package/dist/shim/host/device.js
CHANGED
|
@@ -11,10 +11,14 @@
|
|
|
11
11
|
* page (an app framed by the DeSciX shell measures its own frame). A window without
|
|
12
12
|
* matchMedia THROWS: every browser has it, so its absence is a caller passing the wrong object.
|
|
13
13
|
*
|
|
14
|
-
* Its
|
|
14
|
+
* Its three consumers ask it once each, at load:
|
|
15
15
|
* - the host-view call site (apps/ide/src/host-view-entry.js) asks the shell for NoChrome on a phone, CodeSite
|
|
16
16
|
* elsewhere;
|
|
17
|
-
* - STUDIO (lib/features/ide/AuthorView.jsx) opens with its side panels collapsed on a phone
|
|
17
|
+
* - STUDIO (lib/features/ide/AuthorView.jsx) opens with its side panels collapsed on a phone and puts its Hand tool
|
|
18
|
+
* between the camera joysticks (the phone toolbar Move | Hand | Look), and words its mirror "try it" for that
|
|
19
|
+
* surface (lib/features/ide/studio-try-it.js);
|
|
20
|
+
* - FraqtlIde (lib/features/ide/index.jsx) opens the STUDIO tab card with the controls of that surface
|
|
21
|
+
* (presets.js presetExplainerFor).
|
|
18
22
|
* The static build compiles it into both: the stable host-view entry is bundled as its own self-contained module
|
|
19
23
|
* (apps/ide/vite.config.js frqtl-host-view-entry), the IDE's rollup graph carries its own copy of the same source.
|
|
20
24
|
*
|
|
@@ -162,9 +162,18 @@ assertShowcaseIntro(SHOWCASE_INTRO);
|
|
|
162
162
|
// (the egpt-frqtl front page's #proof tiles: each tile's <b> and its sentence), verbatim;
|
|
163
163
|
// `what`, `why`, `how`, `controls` and each `still.alt` are the authored captions.
|
|
164
164
|
const ROSTER = [
|
|
165
|
+
{
|
|
166
|
+
kind: 'rung', id: "cl1d-mirror-route", mode: "3d",
|
|
167
|
+
still: { tick: 3600, alt: "Ten brightly coloured mirror blocks in a ring, seen face-on, one cyan block on the right standing inside the ring out of its place; a thick, glowing band of gold, red, pink and green light swirls around a dark centre holding faint purple and green eddies, and green and pink rays reach out between the mirrors into the dark." },
|
|
168
|
+
title: "Infinite mirror",
|
|
169
|
+
tile: "Ten mirrors in a ring. Light walks the loop. No Fresnel equations.",
|
|
170
|
+
what: "Ten mirrors in a ring. Light goes round and round the loop, reflected and diffused from mirror to mirror, each mirror colouring the light it sends on.",
|
|
171
|
+
why: "Conventional renderers cut every light path off after a set number of bounces and treat shadows, reflections and indirect light as separate effects. Solving the light directly in a 5 m room would take Frontier about 3 billion years.",
|
|
172
|
+
how: "One emitter and ten walls set as mirrors. Light leaves in every direction; shadowing, reflection and obstruction are all live, with no limit on the objects in its path, and the diffusion is real. No Fresnel equations.",
|
|
173
|
+
},
|
|
165
174
|
{
|
|
166
175
|
kind: 'rung', id: "dse-tunnel-rebuild", mode: "3d",
|
|
167
|
-
still: { tick: 7200, alt: "Three red blocks with blue slit walls between them
|
|
176
|
+
still: { tick: 7200, alt: "Three red blocks with blue slit walls between them, seen at a tilt from behind the source; a fan of yellow rays spreads from a point at lower left into the slits, and beyond them, at upper right, a heat-map plate of blue and green squares shows light and dark bands with a red hot spot at its centre." },
|
|
168
177
|
title: "Double slit",
|
|
169
178
|
tile: "Two slits, one particle at a time. Feynman's “only mystery,” with no amplitudes to add.",
|
|
170
179
|
what: "Particles go through two slits one at a time, and bright and dark bands build up on the detector plate below them, live in your browser tab: the pattern Feynman called quantum mechanics' \"only mystery\". EGPT conceptually holds computer science and physics are linked by P=NP and the principle of least action - that is, everything in reality is efficiently computable. The Lean RET, the derivation of EntropyReal from an IIDParticleSource, and the P=NP capstones are modern formalizations of proofs and ideas advanced by Gian-Carlo Rota circa the late 1970s. The EGPT Lean proofs transparently show the most rigorously advanced proofs ever offered for bridging not only quantum and classical mechanics but mathematics and all the other sciences. Watch it run, read its code, and judge for yourself whether this is true P=NP quantum computing.",
|
|
@@ -180,15 +189,6 @@ const ROSTER = [
|
|
|
180
189
|
why: "Games paint glow and god rays onto the finished picture as a separate screen effect, aimed from where the sun is on the screen rather than from any light in the scene.",
|
|
181
190
|
how: "One sphere emitter, shining in every direction. The god rays are rendered from the density of the particles themselves. No Standard Model, no equations, and the engine does not know whether you are looking at a star, a candle flame or an atom.",
|
|
182
191
|
},
|
|
183
|
-
{
|
|
184
|
-
kind: 'rung', id: "cl1d-mirror-route", mode: "3d",
|
|
185
|
-
still: { tick: 1200, alt: "Ten brightly coloured mirrors in a ring, seen from above; a dense band of gold, orange and green dots circles a dark, empty centre inside it, and faint gold and green rays fan out from that light between the mirrors into the dark space around." },
|
|
186
|
-
title: "Infinite mirror",
|
|
187
|
-
tile: "Ten mirrors in a ring. Light walks the loop. No Fresnel equations.",
|
|
188
|
-
what: "Ten mirrors in a ring. Light goes round and round the loop, reflected and diffused from mirror to mirror, each mirror colouring the light it sends on.",
|
|
189
|
-
why: "Conventional renderers cut every light path off after a set number of bounces and treat shadows, reflections and indirect light as separate effects. Solving the light directly in a 5 m room would take Frontier about 3 billion years.",
|
|
190
|
-
how: "One emitter and ten walls set as mirrors. Light leaves in every direction; shadowing, reflection and obstruction are all live, with no limit on the objects in its path, and the diffusion is real. No Fresnel equations.",
|
|
191
|
-
},
|
|
192
192
|
{
|
|
193
193
|
kind: 'rung', id: "r5-fountain-pool",
|
|
194
194
|
still: { tick: 300, alt: "A dark teal surface scattered with white glints like light on water, with a faint ring around a dark dot at its centre." },
|
|
@@ -258,7 +258,9 @@ export var WALKTHROUGH = [
|
|
|
258
258
|
'tall/thin mirrors sit at the vertices of a regular decagon; each mirror\'s tilt is ' +
|
|
259
259
|
'solved from its incoming/outgoing chords so the reflected centre-line hits the next ' +
|
|
260
260
|
'mirror exactly, and by the star\'s symmetry the tenth leg feeds straight back into ' +
|
|
261
|
-
'the first — the polygon closes on itself.
|
|
261
|
+
'the first — the polygon closes on itself. The scene OPENS with two mirrors (m4, m6) standing ' +
|
|
262
|
+
'off their vertices, so the ring starts broken and the light spills: drag one home with the ' +
|
|
263
|
+
'Hand and watch the field reshape live. Each mirror is a DIFFERENT colour and, on ' +
|
|
262
264
|
'collision, TAGS the beam with its own colour (its drawn colour = its identity): so a ' +
|
|
263
265
|
'particle\'s colour history IS its bounce sequence — red after m1, orange after m2, … ' +
|
|
264
266
|
'magenta after m10, then red again as it re-enters the loop (a pkg-node colour-transition ' +
|
|
@@ -280,19 +282,26 @@ export var WALKTHROUGH = [
|
|
|
280
282
|
'plateaus and FPS stays healthy while the circuit runs indefinitely — and the gate ' +
|
|
281
283
|
'survives Restart (it is captured descriptor DATA, not recipe code).',
|
|
282
284
|
source: 'scenes/mirror-route.js',
|
|
283
|
-
// 3D FIRST = the rung's DEFAULT mode (the first declared entry)
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
285
|
+
// 3D FIRST = the rung's DEFAULT mode (the first declared entry), and this rung is FIRST
|
|
286
|
+
// CONTACT (FIRST_CONTACT_WALKTHROUGH_ID below). It opens face-on to the ring (the
|
|
287
|
+
// 'table-top' preset's axes: azimuth 0, elevation 0, roll 0) but closer than that preset's
|
|
288
|
+
// 780, at distance 440, so the top and bottom slabs sit near the canvas's top and bottom
|
|
289
|
+
// edges. The look is the one a reference STUDIO session exported for this scene: the
|
|
290
|
+
// particles hidden (renderBaseCanvas false), so the picture is the light field alone — strong
|
|
291
|
+
// 'field' god rays computed from the light particles only (godraySource 'leaf'), so the
|
|
292
|
+
// mirrors shadow the beam and cast no rays — plus a soft glow. Every other render option
|
|
293
|
+
// that session exported sat at the renderer's own default, so it is not restated here.
|
|
288
294
|
// The 2D mode stays one click away on the MODE strip.
|
|
289
295
|
modes: [
|
|
290
296
|
{
|
|
291
297
|
id: '3d', label: '3D',
|
|
292
298
|
renderState: {
|
|
293
299
|
renderer: 'three',
|
|
294
|
-
camera: { orbit:
|
|
295
|
-
render: renderRowsFromBag(
|
|
300
|
+
camera: { orbit: { azimuth: 0, elevation: 0, distance: 440, roll: 0 }, preset: null },
|
|
301
|
+
render: renderRowsFromBag({
|
|
302
|
+
renderBaseCanvas: false, godrays: 3, godrayDensity: 1.085,
|
|
303
|
+
godraySource: 'leaf', godrayMode: 'field', glowIntensity: 0.27
|
|
304
|
+
})
|
|
296
305
|
}
|
|
297
306
|
},
|
|
298
307
|
{ id: '2d', label: '2D' }
|
|
@@ -375,89 +384,26 @@ export var WALKTHROUGH = [
|
|
|
375
384
|
renderState: {
|
|
376
385
|
render: [{ key: 'colorScheme', value: 'roygbiv' }]
|
|
377
386
|
},
|
|
378
|
-
// 3D FIRST = the rung's DEFAULT mode (the first declared entry)
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
// The 2D mode stays one click away on the MODE strip.
|
|
387
|
+
// 3D FIRST = the rung's DEFAULT mode (the first declared entry): the experiment
|
|
388
|
+
// standing up in space with a camera you can move. The 2D mode stays one click away
|
|
389
|
+
// on the MODE strip.
|
|
382
390
|
modes: [
|
|
383
391
|
{
|
|
384
392
|
id: '3d', label: '3D',
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
// target (138, 200, 0) — the midpoint of the WORKING LENGTH (emitter
|
|
393
|
-
// x=20 → detector far face x=256) at the beam height
|
|
394
|
-
// (H/2): (20 + 256)/2 = 138. It is DERIVED from the
|
|
395
|
-
// geometry, so it moves whenever the detector does. The
|
|
396
|
-
// default centre is the CANVAS centre x=300, which sits
|
|
397
|
-
// PAST the detector: orbiting about it swings the
|
|
398
|
-
// experiment around a point outside itself and pushes
|
|
399
|
-
// the near objects into the lens. The experiment orbits
|
|
400
|
-
// about the experiment.
|
|
401
|
-
// azimuth -60 → sin az = -0.866 ⇒ camera x = 138 - 0.82·d ≈ -257 at
|
|
402
|
-
// d = 480: BEHIND the emitter, looking down +x through the
|
|
403
|
-
// slit wall (x = 96..160) toward the detector (x = 236).
|
|
404
|
-
// cos az = 0.5 swings it 30° off that axis in z — the
|
|
405
|
-
// "slightly rotated" part: dead-on, the slit wall would
|
|
406
|
-
// eclipse the detector completely; at 30° the detector's
|
|
407
|
-
// own hit map shows PAST the barrier edge, partially, which
|
|
408
|
-
// is exactly the invitation to orbit.
|
|
409
|
-
// elevation -18 → sin el < 0 ⇒ a smaller world y = ABOVE the beam plane
|
|
410
|
-
// (y-down): a gentle look down onto it, so the slit plate
|
|
411
|
-
// and the tunnels read as solids rather than edge-on lines.
|
|
412
|
-
// distance 480 → the full 256-px working length and the 400-px canvas
|
|
413
|
-
// height inside the fixed 50° vertical FOV, with the
|
|
414
|
-
// quanta streaming through the slits still legible. It
|
|
415
|
-
// frames the working length, so it is re-derived with the
|
|
416
|
-
// geometry — and by MEASUREMENT (project the scene's
|
|
417
|
-
// bounding box through the camera basis and read the frame
|
|
418
|
-
// fill: 480 gives max|ndc| 0.851), never by scaling the
|
|
419
|
-
// span, which over-corrects because the working length runs
|
|
420
|
-
// mostly ALONG the view direction and foreshortens.
|
|
421
|
-
// roll +90 → the SLIT-SEPARATION AXIS onto the screen HORIZONTAL.
|
|
422
|
-
// Derived from the scene like the rest, and forced by a
|
|
423
|
-
// constraint 2D imposes on 3D: 2D has only x and y, so with
|
|
424
|
-
// the beam on x the ONLY transverse axis available is y —
|
|
425
|
-
// the slits MUST split along y and the fringes MUST spread
|
|
426
|
-
// along y. 3D reuses that one setup (2D≡3D is this rung's
|
|
427
|
-
// headline), so it inherits a floor-to-ceiling wall with the
|
|
428
|
-
// detector eclipsed behind it. No orbit parameter can fix
|
|
429
|
-
// that: `elevation` moves the eye along ±y, which is the
|
|
430
|
-
// slit-separation axis itself, so "getting above it" slides
|
|
431
|
-
// the eye ALONG the pattern instead of over the wall. ROLL is
|
|
432
|
-
// the missing degree of freedom — it turns the VIEWER about
|
|
433
|
-
// the beam axis, leaving the world untouched, and lands world
|
|
434
|
-
// y (fringe spread) on the screen horizontal and world z (the
|
|
435
|
-
// corridor extrusion) on the vertical: a WIDE, low plate with
|
|
436
|
-
// the two slots SIDE BY SIDE and the fringes reading as bands
|
|
437
|
-
// left-to-right — the textbook DSE picture. The plate's
|
|
438
|
-
// on-screen bounding box goes 182×648 (tall) → 648×182 (wide);
|
|
439
|
-
// the framing is otherwise IDENTICAL, since a roll moves
|
|
440
|
-
// neither the eye nor the radius.
|
|
441
|
-
// THE SIGN is the visually approved one. Measured through
|
|
442
|
-
// `_orbitCameraBasis` at this exact pose (detector plate
|
|
443
|
-
// corners, 600×600): at roll +90 the low-y corner projects to
|
|
444
|
-
// screen-x 20.9 px and the high-y corner to 633.0 px (+y →
|
|
445
|
-
// RIGHT); at roll −90 they land at 579.1 px and −33.0 px
|
|
446
|
-
// (+y → LEFT). Both are equally "wide".
|
|
393
|
+
// THE DSE'S OPENING FRAMING, a choice about what to show first, fitted to a reference STUDIO
|
|
394
|
+
// picture: close in, rolled so the barrier runs from upper left down to lower right
|
|
395
|
+
// across the canvas with its two slits near the centre, the detector plate above and
|
|
396
|
+
// behind it showing its bands and running off the top edge, and the emitter's
|
|
397
|
+
// god-ray fan spreading from the lower left. The orbit revolves about
|
|
398
|
+
// `target`, the midpoint of the working length (emitter x=20 to the detector's
|
|
399
|
+
// far face) at the beam height H/2, so it moves whenever the detector does.
|
|
447
400
|
// preset: null — deliberately NOT a named preset. `setPreset` is a FULL
|
|
448
|
-
// framing reset
|
|
449
|
-
//
|
|
450
|
-
// way back — and exactly why they cannot express this pose,
|
|
451
|
-
// whose whole point is the scene-derived `target` above.
|
|
452
|
-
// NOT a compensator: the former azimuth:38 papered over a renderer defect
|
|
453
|
-
// (a ferried canvas drawn with a y-up UV assumption in the y-down world,
|
|
454
|
-
// on a single-sided plane), fixed at the root — orientation is derived in
|
|
455
|
-
// world space and both broad faces carry the map, so every orbit is now
|
|
456
|
-
// equally correct. This pose is a CHOICE about what to show first.
|
|
401
|
+
// framing reset (it forces `target: null` and `roll: 0`), so the
|
|
402
|
+
// preset buttons are the way back and cannot express this pose.
|
|
457
403
|
renderState: {
|
|
458
404
|
renderer: 'three',
|
|
459
405
|
camera: {
|
|
460
|
-
orbit: { azimuth: -
|
|
406
|
+
orbit: { azimuth: -26, elevation: -18, distance: 440, roll: -115,
|
|
461
407
|
target: { x: 177.5, y: 200, z: 0 } },
|
|
462
408
|
preset: null
|
|
463
409
|
}
|
|
@@ -737,15 +683,17 @@ export function effectiveRenderState(rung, modeId) {
|
|
|
737
683
|
* than silently landing on a blank canvas.
|
|
738
684
|
*
|
|
739
685
|
* ── Layer 2: what it shows ───────────────────────────────────────────────────
|
|
740
|
-
* The
|
|
741
|
-
*
|
|
742
|
-
*
|
|
743
|
-
* PRODUCT INTENT — the first thing FRQTL shows is
|
|
744
|
-
*
|
|
686
|
+
* The infinite mirror (the ten-mirror {10/3} star) in its DEFAULT (3D) mode, face-on:
|
|
687
|
+
* the light field itself, shadowed and reflected live by the mirrors, with two
|
|
688
|
+
* mirrors standing off their star positions so the first thing to try is dragging
|
|
689
|
+
* one home with the Hand. PRODUCT INTENT — the first thing FRQTL shows is light
|
|
690
|
+
* doing what light does, computed live, with objects that are obviously yours to move.
|
|
691
|
+
* The showcase roster's first tile (exhibits.js EXHIBITS[0]) is this rung too; its
|
|
692
|
+
* test holds the two equal.
|
|
745
693
|
*
|
|
746
694
|
* @type {string}
|
|
747
695
|
*/
|
|
748
|
-
export var FIRST_CONTACT_WALKTHROUGH_ID = '
|
|
696
|
+
export var FIRST_CONTACT_WALKTHROUGH_ID = 'cl1d-mirror-route';
|
|
749
697
|
|
|
750
698
|
/**
|
|
751
699
|
* walkthroughById(id) — id → rung, or null. The ONE resolver every consumer of a
|
|
@@ -175,6 +175,26 @@ function bytesToBase64(bytes) {
|
|
|
175
175
|
return btoa(bin);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
/**
|
|
179
|
+
* base64ToBytes(b64) — the inverse of `bytesToBase64`: an envelope's `encoded.bytes` back to the bytes a
|
|
180
|
+
* consumer saves or plays (a page turning a recording into a file). One encoding, one owner of both directions.
|
|
181
|
+
*
|
|
182
|
+
* @param {string} b64
|
|
183
|
+
* @returns {Uint8Array}
|
|
184
|
+
*/
|
|
185
|
+
function base64ToBytes(b64) {
|
|
186
|
+
if (typeof b64 !== 'string') {
|
|
187
|
+
_fail('base64ToBytes expects the base64 string of encoded.bytes, got ' + typeof b64);
|
|
188
|
+
}
|
|
189
|
+
if (typeof atob !== 'function') {
|
|
190
|
+
_fail('base64ToBytes needs a global atob (every browser, and node >= 16)');
|
|
191
|
+
}
|
|
192
|
+
var bin = atob(b64);
|
|
193
|
+
var out = new Uint8Array(bin.length);
|
|
194
|
+
for (var i = 0; i < bin.length; i++) { out[i] = bin.charCodeAt(i); }
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
/**
|
|
179
199
|
* Compose a single-capture envelope.
|
|
180
200
|
*
|
|
@@ -274,6 +294,7 @@ export {
|
|
|
274
294
|
ENCODED_KEYS,
|
|
275
295
|
ENCODERS,
|
|
276
296
|
bytesToBase64,
|
|
297
|
+
base64ToBytes,
|
|
277
298
|
stillEnvelope,
|
|
278
299
|
videoEnvelope
|
|
279
300
|
};
|
|
@@ -5,29 +5,24 @@
|
|
|
5
5
|
*
|
|
6
6
|
* The values are the FINAL FX bag EXACTLY as played and captured in an export session
|
|
7
7
|
* (new_sun.png is the primary oracle; sun.png stays the historical dense reference), verbatim from
|
|
8
|
-
* the exported renderState. They are in the write door's shape — a BAG, what
|
|
8
|
+
* the exported renderState, plus `godrayMode: 'radial'`, the mode chosen for the sun once the modes existed. They are in the write door's shape — a BAG, what
|
|
9
9
|
* `runner.setRenderOptions(bag)` takes — and every key is a capability the three renderer declares.
|
|
10
10
|
*
|
|
11
11
|
* SunLook.SUN_RENDER — the sun's whole look (rung r8-sun's render rows).
|
|
12
|
-
* SunLook.SUN_GODRAYS — the god-ray pair, PICKED from SUN_RENDER: what another scene lights up
|
|
13
|
-
* "at the sun's settings" with (the mirror's 3D mode, the 3D DSE's switch).
|
|
14
12
|
*
|
|
15
|
-
* `
|
|
13
|
+
* `godrayMode` 'radial' is the sun's own (its rays fan from one light point, the source frames' centroid).
|
|
16
14
|
* The camera is NOT here: a pose is a rung row's view, not a look.
|
|
17
15
|
*
|
|
18
|
-
* Readers: `lib/shim/registry/walkthrough.js` (a named import)
|
|
19
|
-
*
|
|
16
|
+
* Readers: `lib/shim/registry/walkthrough.js` (a named import); a scene sketch may import it too
|
|
17
|
+
* (`@descix/frqtl-web/shim/render/sun-look.js`). Zero physics: render
|
|
20
18
|
* options are read only by the renderer.
|
|
21
19
|
*/
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
var SUN_RENDER = Object.freeze({
|
|
25
23
|
shape: 'sphere', colorScheme: 'warmcool',
|
|
26
|
-
godrays: 1.08, godrayDensity: 1.085,
|
|
24
|
+
godrays: 1.08, godrayDensity: 1.085, godrayMode: 'radial',
|
|
27
25
|
glowIntensity: 0.93, heat: 0.35, smoke: 0
|
|
28
26
|
});
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
var SUN_GODRAYS = Object.freeze({ godrays: SUN_RENDER.godrays, godrayDensity: SUN_RENDER.godrayDensity });
|
|
32
|
-
|
|
33
|
-
export { SUN_RENDER, SUN_GODRAYS };
|
|
28
|
+
export { SUN_RENDER };
|
package/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @descix/frqtl-node
|
|
2
2
|
|
|
3
3
|
> The FRQTL physics engine (compiled Rust → WASM) for node: the same modules as @descix/frqtl-web, plus
|
|
4
|
-
> `bootNode` and the hosted eyes. 0.6.1-p3.
|
|
4
|
+
> `bootNode` and the hosted eyes. 0.6.1-p3.4 on npm dist-tag `next`. Node ≥ 22.12.
|
|
5
5
|
|
|
6
6
|
- Install: `npm install @descix/frqtl-node`
|
|
7
7
|
- `import { bootNode, Wall } from '@descix/frqtl-node'` (require() works too); `await bootNode()` → the engine handle.
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
- `./project`: `linkProject` links a scene file against this package's own copy.
|
|
10
10
|
- `./host-view`: `declareHostView`, `HOST_VIEW_OUTCOMES`, `CONTRACT_VIOLATIONS`, `HOST_SDK_SCRIPT` — the same declarer as @descix/frqtl-web/host-view, pure, for a node test of a call site.
|
|
11
11
|
- `./register`: `node --import @descix/frqtl-node/register scene.mjs` resolves a scene's `@descix/frqtl-web/...` imports to this package.
|
|
12
|
-
- Full agent guide: https://cdn.jsdelivr.net/npm/@descix/frqtl-node@0.6.1-p3.
|
|
12
|
+
- Full agent guide: https://cdn.jsdelivr.net/npm/@descix/frqtl-node@0.6.1-p3.4/CLAUDE.md
|
|
13
13
|
- Acceptance is visual; no CLI ships. No account, credits or tokens are needed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@descix/frqtl-node",
|
|
3
|
-
"version": "0.6.1-p3.
|
|
3
|
+
"version": "0.6.1-p3.4",
|
|
4
4
|
"description": "FRQTL for node \u2014 the discrete fractal physics engine (compiled Rust \u2192 WASM), its parts as ES modules, the node engine door (bootNode) and the hosted eyes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|