@descix/frqtl-node 0.6.1-p3.4 → 0.7.0-p0

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 (64) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +4 -3
  3. package/dist/dist-manifest.json +26 -56
  4. package/dist/frqtl/experiments/_shared/empty-sketch.js +17 -0
  5. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +6 -6
  6. package/dist/frqtl/objects/setup/setupFxSmoke.js +2 -2
  7. package/dist/frqtl/objects/setup/setupG3Collide.js +1 -1
  8. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +2 -2
  9. package/dist/frqtl/objects/v1-replacements.js +1 -1
  10. package/dist/frqtl/parts/OvenBox.js +2 -2
  11. package/dist/frqtl/parts/SphereEmitter.js +1 -1
  12. package/dist/shim/engine/router.js +18 -6
  13. package/dist/shim/engine/site-assemble.mjs +268 -0
  14. package/dist/shim/engine/site-graph.js +42 -14
  15. package/dist/shim/host/device.js +1 -1
  16. package/dist/shim/index.js +3 -7
  17. package/dist/shim/notebook/catalog.js +2 -2
  18. package/dist/shim/notebook/format.js +1 -1
  19. package/dist/shim/notebook/knob.js +1 -1
  20. package/dist/shim/notebook/notebook-knobs.js +1 -1
  21. package/dist/shim/project/specifiers.js +8 -8
  22. package/dist/shim/registry/copy-gate.js +3 -3
  23. package/dist/shim/registry/index.js +9 -12
  24. package/dist/shim/registry/roster-link.js +142 -0
  25. package/dist/shim/registry/roster.js +595 -0
  26. package/dist/shim/registry/scene-source.js +80 -105
  27. package/dist/shim/registry/studio-landing.js +15 -7
  28. package/dist/shim/render/sun-look.js +1 -2
  29. package/llms.txt +4 -2
  30. package/package.json +1 -1
  31. package/dist/frqtl/scenes/absorbing-wall-3d.js +0 -37
  32. package/dist/frqtl/scenes/absorbing-wall.js +0 -36
  33. package/dist/frqtl/scenes/atomic-model.js +0 -91
  34. package/dist/frqtl/scenes/big-bang.js +0 -49
  35. package/dist/frqtl/scenes/blackbody.js +0 -84
  36. package/dist/frqtl/scenes/circuit-sat.js +0 -131
  37. package/dist/frqtl/scenes/collision-matrix-3d.js +0 -110
  38. package/dist/frqtl/scenes/counting-wall-3d.js +0 -40
  39. package/dist/frqtl/scenes/counting-wall.js +0 -42
  40. package/dist/frqtl/scenes/double-slit.js +0 -34
  41. package/dist/frqtl/scenes/dse-one-emitter.js +0 -200
  42. package/dist/frqtl/scenes/dse-one-slit.js +0 -118
  43. package/dist/frqtl/scenes/dse-photoelectric.js +0 -74
  44. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +0 -279
  45. package/dist/frqtl/scenes/dse-two-emitters.js +0 -201
  46. package/dist/frqtl/scenes/dse-two-slits.js +0 -136
  47. package/dist/frqtl/scenes/empty.js +0 -17
  48. package/dist/frqtl/scenes/laser-buildup.js +0 -56
  49. package/dist/frqtl/scenes/mirror-3d.js +0 -60
  50. package/dist/frqtl/scenes/mirror-route.js +0 -164
  51. package/dist/frqtl/scenes/mirror.js +0 -42
  52. package/dist/frqtl/scenes/omni-3d.js +0 -30
  53. package/dist/frqtl/scenes/omni-source.js +0 -25
  54. package/dist/frqtl/scenes/self-interference.js +0 -42
  55. package/dist/frqtl/scenes/smoking-gun.js +0 -126
  56. package/dist/frqtl/scenes/straight-walk.js +0 -25
  57. package/dist/frqtl/scenes/sun.js +0 -42
  58. package/dist/frqtl/scenes/two-source.js +0 -42
  59. package/dist/frqtl/scenes/wave.js +0 -29
  60. package/dist/shim/engine/artifact-shell.js +0 -211
  61. package/dist/shim/registry/exhibits.js +0 -436
  62. package/dist/shim/registry/scene-roster.js +0 -35
  63. package/dist/shim/registry/showcase-link.js +0 -61
  64. package/dist/shim/registry/walkthrough.js +0 -752
@@ -1,752 +0,0 @@
1
- /* Copyright 2023-2026 by Essam Abadir */
2
-
3
- /**
4
- * WALKTHROUGH — the ordered "Walk-through" rung registry (rungs in addObject form).
5
- *
6
- * An ORDERED array of rung recipes surfaced as a "Walk-through" group in STUDIO's
7
- * experiment dropdown. Each rung loads via the blank-page load-by-eval path exactly
8
- * like a saved project — the SAME array is the SINGLE authority for BOTH the
9
- * dropdown group AND the `walkthrough:<id>` lookup (no parallel id→source map, no
10
- * switch statement).
11
- *
12
- * A rung row carries the PATH of its scene, never its text: each scene is ONE sketch
13
- * file, `lib/frqtl/scenes/<file>.js` — a dual-mode `buildExperiment(exp)` module that
14
- * holds the whole scene. A host reads it as a project through `projectOf(id | rung, opts)`
15
- * (./scene-source.js: frqtl.json + sketch.js, with effectiveRenderState beside it; a fetch in a
16
- * page, a file read in node; FAIL-LOUD on an unknown id, a non-200 or an empty body). Rungs
17
- * that share one physics name the SAME file.
18
- *
19
- * ── Rung descriptor ──────────────────────────────────────────────────────────
20
- * id : stable dropdown id (the `walkthrough:<id>` selection value)
21
- * title : ordered display label
22
- * prose : one-line declarative description (what it shows, not a journey)
23
- * source : the frqtl-relative path of the scene file, `'scenes/<file>.js'`
24
- * (resolved through the router's `scenes/` profile key).
25
- * renderState : OPTIONAL opaque bag (normalizeSavedRenderState shape). The
26
- * walkthrough load path ferries it into the SAME `_egptPendingRenderState`
27
- * seam the `project:` path uses (E2a shim-owned contract) — renderer/
28
- * seed thread as URL overrides (load-time); render options +
29
- * camera replay after start. NO new mechanism. Absent → 2D/p5 default.
30
- * modes : OPTIONAL ordered list of named mode entries — `[{ id, label,
31
- * renderState? }, …]`. The FIRST entry is the rung's DEFAULT mode.
32
- * Absent (or length 1) → a single-mode rung: zero UI, byte-identical
33
- * behavior. A mode entry's `renderState` is a DELTA merged over the
34
- * rung's base `renderState` by the ONE derivation point,
35
- * `effectiveRenderState(rung, modeId)` (below) — never merged by hand
36
- * at a call site. The dropdown SELECTION VALUE stays `walkthrough:<id>`
37
- * in every mode (a mode is never a second experiment id); STUDIO tracks
38
- * the active mode in `project.walkthroughMode` and re-runs the ordinary
39
- * walkthrough load path with the effective bag (a reload — the same
40
- * pattern as `?engine=`).
41
- *
42
- * ── The mode-aware recipe form (S3/W2) ────────────────────────────────────────
43
- * A multi-mode rung has exactly ONE `source`. A mode selects the RENDERER the host boots
44
- * (`renderState.renderer`); the scene reads the booted display once, before its world, and
45
- * makes the matching world (`three` ⇒ exp.createScene, else exp.createCanvas) — its
46
- * dimensional deltas branch on that. PER-MODE SCENE FILES ARE REJECTED: a second source
47
- * is the config-mirror anti-pattern this contract exists to kill. Every mode delta
48
- * must be expressible as DECLARED descriptor fields (emitter `is2D`, Wall/Tunnel
49
- * `z`/`depth`, ordinary Walls for the z-collimators, camera on the renderState) so
50
- * a built scene's declared parts hold concrete, mode-PINNED state and survive Restart.
51
- *
52
- * ── The recipe form (a parts block, written back by the parts writer) ─────────
53
- * Rungs are OBJECT recipes: each body states the WORLD first, in its co-init call
54
- * (`exp.createCanvas(…)` / `exp.createScene(…)`), then declares its parts —
55
- * `const p = exp.declare(parts)` over the module's `export const parts = { name: [Kind, props] }`
56
- * block — a PART never inits the world, the sketch does, once, before any part. Every
57
- * declared part is a real authored object, so a loaded rung is DRAGGABLE / INSPECTABLE,
58
- * and a STUDIO hand (grab/drag, Inspector, knob, visibility) is written into the SAME
59
- * sketch text by the parts writer, `await exp.toSketch()`: it patches the touched
60
- * entries of the parts block in place and leaves every other line of the file as authored.
61
- *
62
- * ── Layer 2: physics behavior ────────────────────────────────────────────────
63
- * Zero new physics (canonical-bench SU1) — every rung composes already-goldened
64
- * engine behaviors via the shared sphere-emitter sugar. Each rung un-degenerates ONE
65
- * already-present input of the previous (Q-THEO-1: strictly compositional).
66
- * The addObject builder forwards the opts VERBATIM to setupSphereEmitter3D, so the
67
- * physics/visual is BYTE-IDENTICAL to the earlier factory-call rungs.
68
- */
69
-
70
- 'use strict';
71
-
72
- // The ONE shim-level renderState normalizer (E2a) — `effectiveRenderState` merges
73
- // through it so a mode bag can never drift from the saved-project bag shape.
74
- import { normalizeSavedRenderState, renderRowsFromBag } from '../render-state/index.js';
75
- // The sun's look — the ONE owner of its render values.
76
- import * as SunLook from '../render/sun-look.js';
77
-
78
- // ── Shared render-variant renderState deltas ─────────────────────────────────
79
- // All ride the SAME filter.<name>->setFilterActive / setRenderOption ControlBind
80
- // dispatch a saved project's renderState uses (E2a/S6 seam; p5 supplies the filters).
81
-
82
- // Fountain-pool: hide particles + water shader (Rung 5, Rung 6B).
83
- var WATER_RENDER_STATE = {
84
- render: [
85
- { key: 'renderBaseCanvas', value: false },
86
- { key: 'filter.water', value: true }
87
- ]
88
- };
89
- // Plasma fusion: particles ON + water + additive bloom on the live field (Rung 6C).
90
- var PLASMA_RENDER_STATE = {
91
- render: [
92
- { key: 'renderBaseCanvas', value: true },
93
- { key: 'filter.water', value: true },
94
- { key: 'filter.bloom', value: true }
95
- ]
96
- };
97
- // 3D: the three renderer (the scene's exp.createScene makes the 3D world + perspective camera).
98
- var THREE_RENDER_STATE = {
99
- renderer: 'three',
100
- camera: { orbit: null, preset: 'rooftop' }
101
- };
102
- // Sun: 3D + the sun's look. The render VALUES are owned by lib/shim/render/sun-look.js (an
103
- // exported FX bag); the camera is this row's own view, verbatim from an exported renderState.
104
- var SUN_RENDER_STATE = {
105
- renderer: 'three',
106
- camera: { orbit: { azimuth: 32.978, elevation: 45, distance: 2873.68 }, preset: null },
107
- render: renderRowsFromBag(SunLook.SUN_RENDER)
108
- };
109
-
110
- /** @type {Array<{id:string, title:string, prose:string, source:string, renderState?:object}>} — `source` is a 'scenes/<file>.js' path */
111
- export var WALKTHROUGH = [
112
- {
113
- id: 'r1-straight-walk',
114
- title: '1 · Straight Walk',
115
- prose: 'A collimated line of quanta drifts steadily along +X, each carrying a small ' +
116
- 'always-armed transverse ripple (λ=16) — the walk in its simplest form.',
117
- source: 'scenes/straight-walk.js'
118
- },
119
- {
120
- id: 'r2-wave',
121
- title: '2 · Wave',
122
- prose: 'The same straight walk, but the transverse coin is given leverage — the ripple ' +
123
- 'grows into a visible snake as each leaf oscillates with a longer wavelength.',
124
- source: 'scenes/wave.js'
125
- },
126
- {
127
- id: 'r3-omni-source',
128
- title: '3 · Omni Source',
129
- prose: 'Release the walk in every direction at once — a single point source spraying an ' +
130
- 'even disk of quanta (the sun). Mixed-axis momentum un-degenerates the direction sampler.',
131
- source: 'scenes/omni-source.js'
132
- },
133
- {
134
- id: 'r4-self-interference',
135
- title: '4 · Self-Interference',
136
- prose: 'A single omni source of oppositely-charged quanta launched from a ring — the ' +
137
- 'overlapping charged walks braid into radial bands of light and dark, an interference ' +
138
- 'pattern with no wave equation.',
139
- source: 'scenes/self-interference.js'
140
- },
141
- {
142
- // POSITION (provisional): placed here because
143
- // Self-Interference is its closest physics sibling — the same banded fan, reached by
144
- // building an apparatus instead of by launching an omni source. Using the letter-suffix
145
- // idiom the 6A/6B/6C rungs already establish, so NO existing rung is renumbered.
146
- // Moving it (e.g. to the end, after Sun) is a one-line array move.
147
- id: 'r4b-laser-buildup',
148
- title: '4B · Laser Build-Up',
149
- prose: 'A laser, assembled from parts you have already met. A GAIN SOURCE — rung 3\'s omni ' +
150
- 'emitter, restricted to a forward half-fan — fires into a RESONANT BORE whose walls ' +
151
- 're-aim every hit forward instead of reflecting it, and the beam leaves through an ' +
152
- 'OUTPUT COUPLER at the far mouth. The coupler has two independent switches: RECENTER ' +
153
- 'collapses every exiting quantum onto the bore\'s mid-line, making the mouth a single ' +
154
- 'point source; PHASE RESET re-locks the ensemble\'s oscillation so equal-radius quanta ' +
155
- 'share a phase again. Recentering alone gives you the geometry of rung 4; adding phase ' +
156
- 'gives you its coherence. Sweep the two switches against the wavelength and the bore ' +
157
- 'length: at long wavelengths the mouth is already nearly in phase and the bands appear ' +
158
- 'on their own, while at short ones the fan reads smooth until you switch phase on. ' +
159
- 'Nothing here is a wave equation — it is the same charged walk, aimed and re-timed.',
160
- source: 'scenes/laser-buildup.js'
161
- },
162
- {
163
- id: 'r5-fountain-pool',
164
- title: '5 · Fountain Pool',
165
- prose: 'The SAME self-interference field, worn as water — particles hidden and the water ' +
166
- 'shader on. The light field and the water field are the same emergent wave, a ' +
167
- 'different lens (the fractal aha).',
168
- // SAME physics as Rung 4 (the same scene file) — render delta only.
169
- source: 'scenes/self-interference.js',
170
- renderState: WATER_RENDER_STATE
171
- },
172
- {
173
- id: 'r6a-two-source',
174
- title: '6A · Two Sources',
175
- prose: 'Two independent self-interference sources, ±32px apart — their overlapping charged ' +
176
- 'walks form an interference lattice between them: pure density superposition of two ' +
177
- 'IID populations, interference without a wave equation.',
178
- source: 'scenes/two-source.js'
179
- },
180
- {
181
- id: 'r6b-two-fountains',
182
- title: '6B · Two Fountains',
183
- prose: 'The two-source lattice worn as water — two fountains whose ripple fields overlap ' +
184
- 'into the same interference web, painted by the water shader with the particles hidden.',
185
- // SAME physics as Rung 6A (the same scene file) — render delta only.
186
- source: 'scenes/two-source.js',
187
- renderState: WATER_RENDER_STATE
188
- },
189
- {
190
- id: 'r6c-plasma-fusion',
191
- title: '6C · Plasma Fusion',
192
- prose: 'The two-source field lit as plasma — particles ON, water surface, and an additive ' +
193
- 'bloom, so the two colliding charged streams read as a fusing plasma core.',
194
- // SAME physics as Rung 6A (the same scene file) — render delta only.
195
- source: 'scenes/two-source.js',
196
- renderState: PLASMA_RENDER_STATE
197
- },
198
- {
199
- id: 'r7-3d',
200
- title: '7 · 3D',
201
- prose: 'The omni source released into all three dimensions — a true 3D ball of quanta ' +
202
- 'rendered with three.js, orbit-able with the mouse or the on-screen camera joysticks.',
203
- source: 'scenes/omni-3d.js',
204
- renderState: THREE_RENDER_STATE
205
- },
206
- {
207
- id: 'r8-sun',
208
- title: '8 · Sun',
209
- prose: 'The finale — the 3D omni point source dressed as a star: a dense (mass-16, fullness-1) ' +
210
- 'warm-cool glowing core with volumetric god-rays and a shimmer of heat. The same walk ' +
211
- 'that drew the pool, now the sun.',
212
- source: 'scenes/sun.js',
213
- renderState: SUN_RENDER_STATE
214
- },
215
- // ── Collision Lab (W1) — the wall-collision primitives,
216
- // built on the first-class v2 Wall. Scaffolds for interactive tuning (drag /
217
- // rotate / select / save) that confirm the base object + control behaviors
218
- // work bug-free. SU1 — the Walls add zero physics.
219
- {
220
- id: 'cl1a-mirror',
221
- title: '1A · Mirror',
222
- group: 'Collision Lab',
223
- prose: 'The first true routing demo: a LASER — a genuinely collimated +x beam (the ' +
224
- 'tightest emission the emitter supports: pure +x momentum, a single charge, no ' +
225
- 'wavelength-scale spreading) — fires into a rotatable mirror. The mirror compiles ' +
226
- 'the real reflection law: a surface tilted to θ deflects the beam by 2θ (standard ' +
227
- 'optics), so at 45° the thin beam exits straight up. ROTATE the mirror (the ' +
228
- 'orientation slider, or LOOK-x in 3D) and the thin reflected beam steers live.',
229
- source: 'scenes/mirror.js'
230
- },
231
- {
232
- id: 'cl1b-absorbing-wall',
233
- title: '1B · Absorbing Wall',
234
- group: 'Collision Lab',
235
- prose: 'A wall that ABSORBS the quanta into its OWN real mass — it fills as it soaks up ' +
236
- 'arrivals, stops at capacity, and slowly radiates the stored mass back out as ' +
237
- 'lower-dimension leaves. Mass is exactly conserved (the blackbody absorb/re-emit).',
238
- source: 'scenes/absorbing-wall.js'
239
- },
240
- {
241
- id: 'cl1c-counting-wall',
242
- title: '1C · Counting Wall',
243
- group: 'Collision Lab',
244
- prose: 'A detector screen — the Detector Screen preset (destroy + count) tallies every ' +
245
- 'arrival by position. A LIVE in-scene chart (a first-class LiveChart object placed ' +
246
- 'beside the detector) plots the running y→count histogram as the sim runs — the ' +
247
- 'chart reads wall.getCollisionHistogram between ticks and accumulates on-screen. ' +
248
- 'This is the basis for the double-slit fringe readout. The chart panel is a plain ' +
249
- 'render-only object: drag it, resize it, or point it at any counting wall.',
250
- source: 'scenes/counting-wall.js'
251
- },
252
- {
253
- id: 'cl1d-mirror-route',
254
- title: '1D · Mirror Loop (10-mirror {10/3} star)',
255
- group: 'Collision Lab',
256
- prose: 'The explanatory centerpiece: a single 2-PARTICLE-WIDE laser threads a CLOSED ' +
257
- 'TEN-MIRROR {10/3} STAR and LOOPS FOREVER — m1→m2→…→m10→m1→… with no exit. Ten ' +
258
- 'tall/thin mirrors sit at the vertices of a regular decagon; each mirror\'s tilt is ' +
259
- 'solved from its incoming/outgoing chords so the reflected centre-line hits the next ' +
260
- 'mirror exactly, and by the star\'s symmetry the tenth leg feeds straight back into ' +
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 ' +
264
- 'collision, TAGS the beam with its own colour (its drawn colour = its identity): so a ' +
265
- 'particle\'s colour history IS its bounce sequence — red after m1, orange after m2, … ' +
266
- 'magenta after m10, then red again as it re-enters the loop (a pkg-node colour-transition ' +
267
- 'proof shows the dominant next-colour after m_k is m_{k+1} for all ten, closing the ring). ' +
268
- 'Reflection off the DRAWN face is the standard-optics 2θ law (the swept-OBB face ' +
269
- 'collision), ten times over, no wave equation. In 3D (the default mode) every mirror ' +
270
- 'is a SLAB — a plate with real depth — standing at its decagon vertex with the same ' +
271
- 'in-plane tilt, and the laser is volumetric: it braids in y AND z, each slab\'s tilted ' +
272
- 'faces reflect it by the 2θ law (vz passes through) and its near/far z-faces bounce vz, ' +
273
- 'so no mirror passes the beam; ORBIT the camera (LOOK/drag or the az/el sliders) to see ' +
274
- 'the ring as plates in space, and z-strays exit between the slabs and die at the ' +
275
- 'scene\'s z-boundary. Switch the MODE strip to 2D for the flat star — the same ring, ' +
276
- 'tilts, colours and laser, minus the z-axis (2D≡3D). Because the loop has no sink, the ' +
277
- 'beam never leaves — an INVISIBLE background Wall (nothing + count hook) spanning the ' +
278
- 'whole canvas (and, in 3D, the whole volume) counts the live population every tick (its ' +
279
- 'per-tick collided frames ARE the live quanta), and the emitter\'s DECLARED emission ' +
280
- 'gate (emissionCap + gateCounter, the blackbody pattern compiled at construction) reads ' +
281
- 'THAT counter to pause emission at the cap and resume below it, so the population ' +
282
- 'plateaus and FPS stays healthy while the circuit runs indefinitely — and the gate ' +
283
- 'survives Restart (it is captured descriptor DATA, not recipe code).',
284
- source: 'scenes/mirror-route.js',
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.
294
- // The 2D mode stays one click away on the MODE strip.
295
- modes: [
296
- {
297
- id: '3d', label: '3D',
298
- renderState: {
299
- renderer: 'three',
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
- })
305
- }
306
- },
307
- { id: '2d', label: '2D' }
308
- ]
309
- },
310
- // ── Collision Lab · 3D (Z2) — 1A/1B/1C driven in a 3D scene:
311
- // a VOLUMETRIC (is2D:false) emitter + a 3D WALL SLAB (depth → a real z-slab, rendered
312
- // as a BoxGeometry) + the THREE renderer/perspective camera (renderState). The walls'
313
- // z-slabs CONTAIN z=0 (near<0<far) so the beam/spray — emitted at z=0 — collides. The
314
- // reflect/absorb/count physics is the SAME as the 2D rungs (SU1); only the render layer
315
- // and the wall's z-extent change (2D≡3D). Drive with LOOK/orbit (the W0 3D camera).
316
- {
317
- id: 'cl1a-mirror-3d',
318
- title: '1A · Mirror (3D)',
319
- group: 'Collision Lab · 3D',
320
- prose: 'The mirror in a 3D scene, FACE-ON: a directional +x beam hits a mirror SLAB ' +
321
- '(a box you can orbit around) square on its drawn face and bounces STRAIGHT BACK ' +
322
- '— beam in, recolored beam out along the same line, nothing continuing past the ' +
323
- 'plate, from every camera angle (an actual mirror). Orbit (LOOK/drag) to see the ' +
324
- 'retro-reflection in three dimensions; ROTATE the mirror (the orientation slider) ' +
325
- 'to steer the exit by the same 2θ law as the 2D mirror — identical physics, now a ' +
326
- 'box in space (2D≡3D).',
327
- source: 'scenes/mirror-3d.js',
328
- renderState: THREE_RENDER_STATE
329
- },
330
- {
331
- id: 'cl1b-absorbing-wall-3d',
332
- title: '1B · Absorbing Wall (3D)',
333
- group: 'Collision Lab · 3D',
334
- prose: 'The absorbing wall as a 3D slab: a volumetric (spherical) source sprays quanta in ' +
335
- 'three dimensions into a wall SLAB that absorbs them into its own real mass, fills ' +
336
- 'to capacity, and radiates lower-dimension leaves back out — mass exactly conserved. ' +
337
- 'Orbit the camera to watch the spray soak into the box from every angle (2D≡3D).',
338
- source: 'scenes/absorbing-wall-3d.js',
339
- renderState: THREE_RENDER_STATE
340
- },
341
- {
342
- id: 'cl1c-counting-wall-3d',
343
- title: '1C · Counting Wall (3D)',
344
- group: 'Collision Lab · 3D',
345
- prose: 'The detector screen as a 3D slab with a live in-scene chart: a volumetric source ' +
346
- 'sprays into a Detector Screen SLAB (destroy + count); a first-class LiveChart panel ' +
347
- '— placed in the scene beside the detector — plots the running arrivals as the sim ' +
348
- 'runs, textured into the 3D view. Orbit to see the detector box and its chart in ' +
349
- 'space (the basis for a 3D fringe readout).',
350
- source: 'scenes/counting-wall-3d.js',
351
- renderState: THREE_RENDER_STATE
352
- },
353
- // ── DSE — THE double-slit (on the Tunnel object, the single canonical
354
- // double-slit) then the photoelectric experiment, both on the full new
355
- // component set. Letter prefixes are DROPPED: the group label carries the
356
- // family, and the ids are the stable contract.
357
- {
358
- id: 'dse-tunnel-rebuild',
359
- title: 'Double Slit (Tunnel, symmetric)',
360
- group: 'DSE',
361
- prose: 'The double-slit REBUILT ON THE TUNNEL OBJECT at PHYSICAL DSE PROPORTIONS: the ' +
362
- 'detector sits at the SYMMETRIC distance — emitter-to-front of the slit wall EQUALS ' +
363
- 'back of the slit wall-to-detector. Each slit channel is a DIRECTED Tunnel: a hit on ' +
364
- 'a channel wall ejects the quantum back into the bore and re-emits it in a uniformly ' +
365
- 'random direction within 180 degrees of the corridor axis, so each slit acts as a ' +
366
- 'semi-circular emitter — the classical picture of what a slit does to a wavefront — ' +
367
- 'rather than a mirror that would only ever project the beam it was given. The exit ' +
368
- 'angle is drawn with real trig, so the fan is flat, not diagonal-weighted; the ' +
369
- 'barriers + screen are v2 Walls (destroy / detector ' +
370
- 'preset) so the scene survives Restart. The source emits FORWARD over 180 degrees ' +
371
- '(the hemisphere preset) rather than all the way round, so the burst that used to fly ' +
372
- 'backward into the catcher now lights the aperture — the same sampler, restricted to ' +
373
- 'the forward half, aimed at the wall. In 2D a balanced, central-dominant three-lobe ' +
374
- 'interference pattern builds up. Switch the MODE strip to 3D and the SAME experiment stands up in space — ' +
375
- 'the barrier becomes a plate with two slots, each channel a real corridor, the source ' +
376
- 'a volumetric forward half-ball — and the instrument follows: the detector plate paints its OWN hit ' +
377
- 'map on its faces, exactly where each particle landed, so the fringes read as bands on ' +
378
- 'the plate itself. Identical physics, one more axis (2D≡3D).',
379
- source: 'scenes/dse-tunnel-rebuild.js',
380
- // The spectrally correct ramp (LightColor 'roygbiv', no pink terminus) — the right
381
- // instrument for a LIGHT experiment, and the one on which this beam's fullness
382
- // (0.3125) reads as yellow. Declared on the BASE renderState so BOTH modes inherit
383
- // it (effectiveRenderState CONCATENATES base render rows with the mode's).
384
- renderState: {
385
- render: [{ key: 'colorScheme', value: 'roygbiv' }]
386
- },
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.
390
- modes: [
391
- {
392
- id: '3d', label: '3D',
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.
400
- // preset: null — deliberately NOT a named preset. `setPreset` is a FULL
401
- // framing reset (it forces `target: null` and `roll: 0`), so the
402
- // preset buttons are the way back and cannot express this pose.
403
- renderState: {
404
- renderer: 'three',
405
- camera: {
406
- orbit: { azimuth: -26, elevation: -18, distance: 440, roll: -115,
407
- target: { x: 177.5, y: 200, z: 0 } },
408
- preset: null
409
- }
410
- }
411
- },
412
- { id: '2d', label: '2D' }
413
- ]
414
- },
415
- {
416
- id: 'dse-photoelectric',
417
- title: 'Photoelectric',
418
- group: 'DSE',
419
- prose: 'The photoelectric effect as absorb/re-emit: an incoming light stream charges an ' +
420
- 'ABSORBING wall — it fills to capacity (watch the stored bar plateau) and radiates ' +
421
- 'lower-dimension quanta out its back, mass exactly conserved. A live chart shows the ' +
422
- 'wall\'s charge ledger (stored / absorbed / radiated); a second Detector Screen behind ' +
423
- 'the wall counts the radiated photoelectrons as they emerge past it. Incoming light ' +
424
- 'charges the wall, radiated quanta appear beyond it and get counted — the current ' +
425
- 'absorb/radiate drip (the emergent-probability threshold comes later).',
426
- source: 'scenes/dse-photoelectric.js'
427
- },
428
- // ── The canonical-leg pair. The competence baseline: no
429
- // barrier, no slits, the plate ON the leg the ring formula was written for,
430
- // and the graph a DSE researcher actually looks at first. 2D only — one
431
- // declared world each, no mode strip (a physics variant is a recipe
432
- // declaration, never a mode).
433
- {
434
- id: 'dse-one-emitter',
435
- title: 'One Emitter at the Canonical Leg',
436
- group: 'DSE',
437
- prose: 'ONE point source, nothing between it and the plate, and the plate placed at the ' +
438
- 'axial leg D = λ_eff/4 — the leg the published ring formula r_m = (λ/4)·√((2m+1)²+1) ' +
439
- 'was written for, where the "+1" under the root IS the (λ/4)² axial term. At that leg ' +
440
- 'the pattern crosses the wall at TWO points, one above and one below the source, each ' +
441
- 'a quarter-wavelength out: the count-by-y chart standing behind the wall should show a ' +
442
- 'band PAIR at y = 120 and y = 280 on the 600×400 stage, with a sharp minimum just ' +
443
- 'inside each and a flat plateau between them. The plate COUNTS WITHOUT ABSORBING ' +
444
- '(preset transparent-probe), so the reading is the density at the plate plane rather ' +
445
- 'than a first-arrival caustic, and the quanta fly on to a far-edge sink. λ = 320 px is ' +
446
- 'chosen for resolution: the dip-to-peak structure is 0.025λ wide, so a shorter ' +
447
- 'wavelength would smear it into a hump on this stage. The light is yellow because its ' +
448
- 'fullness is 0.3125 — derived, never forced.',
449
- source: 'scenes/dse-one-emitter.js',
450
- renderState: { render: [{ key: 'colorScheme', value: 'roygbiv' }] }
451
- },
452
- {
453
- id: 'dse-two-emitters',
454
- title: 'Two Emitters at the Canonical Leg',
455
- group: 'DSE',
456
- prose: 'The SAME apparatus with a SECOND point source, separated from the first by exactly ' +
457
- 'd = λ_eff/2 — the strongest-coupling case, since two sources reinforce when their ' +
458
- 'separation is a multiple of λ/2 and the coupling falls as (λ/d)². The plate stays at ' +
459
- 'the same leg D = λ_eff/4 from the emitter plane, with the same chart registered to it. ' +
460
- 'Because each source lays its own band a quarter-wavelength above and below itself, and ' +
461
- 'the two sources sit a half-wavelength apart, source 1\'s LOWER band and source 2\'s ' +
462
- 'UPPER band land on the SAME line — the midline y = 200, equidistant from both ' +
463
- 'emitters. So the chart should show a CENTRAL peak where the one-emitter rung has none, ' +
464
- 'flanked by the two outer bands at y = 40 and y = 360. That contrast — no centre with ' +
465
- 'one source, a doubly-reinforced centre with two — is the whole claim, and it is one ' +
466
- 'glance at two graphs.',
467
- source: 'scenes/dse-two-emitters.js',
468
- renderState: { render: [{ key: 'colorScheme', value: 'roygbiv' }] }
469
- },
470
- {
471
- id: 'dse-two-slits',
472
- title: 'Two Slits at the Canonical Leg',
473
- group: 'DSE',
474
- prose: 'The SAME apparatus as "Two Emitters at the Canonical Leg", but the two point ' +
475
- 'sources are SLIT EXITS in an opaque barrier rather than two free emitters. ONE ' +
476
- 'emitter sits behind the barrier and lights BOTH slits; each slit is a directed ' +
477
- 'Tunnel channel whose exit gate is COLLAPSED and PHASE-RESET, so the mouth stands ' +
478
- 'in for a phase-locked point source at exactly the slit\'s own y — the same ' +
479
- '"configured emitter" composition dse-tunnel-rebuild uses. The slit centres sit at ' +
480
- 'the identical y = 120 and y = 280 the two-emitter rung used, separated by the same ' +
481
- 'd = λ_eff/2, and the plate sits at the identical leg D = λ_eff/4 — now measured from ' +
482
- 'the slit exit plane, this apparatus\'s new source plane. If the slit is a faithful ' +
483
- 'point source, the two rungs must draw the SAME picture: a central peak at y = 200 ' +
484
- '(source 1\'s lower band and source 2\'s upper band coincide there) flanked by outer ' +
485
- 'bands at y = 40 and y = 360.',
486
- source: 'scenes/dse-two-slits.js',
487
- renderState: { render: [{ key: 'colorScheme', value: 'roygbiv' }] }
488
- },
489
- {
490
- id: 'dse-one-slit',
491
- title: 'One Slit at the Canonical Leg',
492
- group: 'DSE',
493
- prose: 'The companion arm to "Two Slits at the Canonical Leg": the IDENTICAL apparatus ' +
494
- '(same emitter, same barrier, same slit-1 aperture at y=120, same leg D, same ' +
495
- 'plate, same chart) with the SECOND slit walled off — one aperture instead of two. ' +
496
- 'A slit exit is a phase-locked point source at its own y, so with only slit 1 open ' +
497
- 'the surviving population is governed by ONE source at y=120: its own ring ladder ' +
498
- 'predicts bands at y=40, y=200, and y=360 (the same three y positions the two-slit ' +
499
- 'rung shows), not a single broad structureless lobe centred on the open slit. This ' +
500
- 'is the actual double-slit claim — presence and position of structure from purely ' +
501
- 'stochastic, non-interacting particles — read as one glance between the two charts.',
502
- source: 'scenes/dse-one-slit.js',
503
- renderState: { render: [{ key: 'colorScheme', value: 'roygbiv' }] }
504
- },
505
-
506
- // ── Impossible things — the front-page scenes that were experiment pages (each a sketch
507
- // file composing the SAME objects, params and seed as the page it replaced).
508
- {
509
- id: 'blackbody',
510
- title: 'Blackbody Radiation',
511
- group: 'Impossible things',
512
- prose: 'A cavity of absorbing walls held at a target number of quanta by its own thermostat. ' +
513
- 'Leaves collide and merge into parent frames; a frame\'s fullness is its frequency, so ' +
514
- 'what leaks out of the slit is a spectrum of frame sizes. The Planck-like curve emerges ' +
515
- 'from collisions — no energy quanta assumed.',
516
- source: 'scenes/blackbody.js'
517
- },
518
- {
519
- id: 'circuit-sat',
520
- title: 'Circuit SAT: Half-Adder',
521
- group: 'Impossible things',
522
- prose: 'Two laser inputs drive rail traces into an XOR gate (SUM) and an AND gate (CARRY). A ' +
523
- 'gate\'s valve opens only when its two input ammeters read its logic, so the circuit ' +
524
- 'settles onto the half-adder\'s answer, read as the current on each grounded readout. ' +
525
- 'No search algorithm runs.',
526
- source: 'scenes/circuit-sat.js'
527
- },
528
- {
529
- id: 'big-bang',
530
- title: 'Big Bang',
531
- group: 'Impossible things',
532
- prose: 'One radial burst of leaves with no initial momentum, in a world that generates parent ' +
533
- 'frames top-down. The uniform field clumps into filaments and voids with no gravity law ' +
534
- 'and no cosmological constant.',
535
- source: 'scenes/big-bang.js'
536
- },
537
- {
538
- id: 'atomic-model',
539
- title: 'Atomic Model',
540
- group: 'Impossible things',
541
- prose: 'Forty-eight quarks in a proton frame, the proton and one electron in a stationary atom ' +
542
- 'frame. Each parent\'s compress pulls its children one pixel toward its centre each tick, ' +
543
- 'so the structure binds and holds with no force law and no quantum numbers.',
544
- source: 'scenes/atomic-model.js'
545
- },
546
- {
547
- id: 'collision-matrix-3d',
548
- title: '3D Collisions Lab',
549
- group: 'Impossible things',
550
- prose: 'Seven head-on lanes of equal-mass voxel pairs at seven depths, from feather-light to ' +
551
- 'rigid. Density sets how hard the bodies are; contact is identical on every axis. The ' +
552
- 'same engine that makes light makes mechanics.',
553
- source: 'scenes/collision-matrix-3d.js',
554
- renderState: { renderer: 'three' }
555
- },
556
- {
557
- id: 'smoking-gun',
558
- title: 'Smoking Gun',
559
- group: 'Smoking Gun',
560
- prose: 'A gun fires and the smoke emerges. Your challenge: make the bullet self-propelled.',
561
- source: 'scenes/smoking-gun.js',
562
- // A side profile centred on the muzzle (az 180: barrel +X reads screen-right), and the
563
- // archived Full look halved, rays from the fundamental layer only.
564
- renderState: {
565
- renderer: 'three',
566
- camera: { orbit: { azimuth: 180, elevation: 8, distance: 480, target: { x: 300, y: 300, z: 0 } },
567
- preset: null },
568
- render: renderRowsFromBag({ glowIntensity: 0.6, godrays: 0.5, godrayDensity: 0.8,
569
- godraySource: 'leaf', heat: 0.2, smoke: 0 })
570
- }
571
- }
572
- ];
573
-
574
- // ── The mode contract (S3/W2) ────────────────────────────────────────────────
575
-
576
- /**
577
- * walkthroughModes(rung) — the rung's declared mode list, ALWAYS an array.
578
- * A rung without `modes` is single-mode: `[]` (the caller renders no UI and
579
- * loads the base renderState). This is the ONE reader of the raw field.
580
- * @param {object} rung
581
- * @returns {Array<{id:string,label:string,renderState?:object}>}
582
- */
583
- export function walkthroughModes(rung) {
584
- return (rung && Array.isArray(rung.modes)) ? rung.modes : [];
585
- }
586
-
587
- /**
588
- * walkthroughGroup(rung) — the rung's LAB GROUP label, always a string.
589
- *
590
- * The ONE reader of the raw `group` field AND the ONE owner of the default label
591
- * a rung that declares none falls into. Every surface that renders the rungs in
592
- * groups (the STUDIO dropdown, the sdk showcase gallery) ferries THIS — spelling
593
- * `rung.group || 'Light Lab'` at a call site is the schema-mirror anti-pattern
594
- * (rename the default in one place and the other surface silently splits the
595
- * group in two).
596
- *
597
- * @param {object} rung — a WALKTHROUGH entry.
598
- * @returns {string}
599
- */
600
- export function walkthroughGroup(rung) {
601
- return (rung && typeof rung.group === 'string' && rung.group.length > 0)
602
- ? rung.group : 'Light Lab';
603
- }
604
-
605
- /**
606
- * defaultWalkthroughModeId(rung) — the rung's DEFAULT mode id (the first declared
607
- * entry), or null for a single-mode rung.
608
- * @param {object} rung
609
- * @returns {string|null}
610
- */
611
- export function defaultWalkthroughModeId(rung) {
612
- var modes = walkthroughModes(rung);
613
- return modes.length > 0 ? modes[0].id : null;
614
- }
615
-
616
- /**
617
- * effectiveRenderState(rung, modeId) — THE ONE derivation point for "what
618
- * renderState does this rung load in this mode".
619
- *
620
- * ── Layer 1: API contract ────────────────────────────────────────────────────
621
- * Merges the selected mode entry's `renderState` DELTA over the rung's base
622
- * `renderState` and returns the NORMALIZED bag (`normalizeSavedRenderState`
623
- * shape) the walkthrough load path already ferries. Merge rules:
624
- * - scalar/object keys (renderer, seed, camera, backdropVisible, …):
625
- * the mode entry WINS when it declares the key;
626
- * - `render` (the ordered setRenderOption rows): base rows THEN mode rows —
627
- * a concat, so a mode row applied later naturally overrides a base row with
628
- * the same key (the same last-write-wins the replay already has).
629
- *
630
- * `modeId` may be null/undefined → the rung's DEFAULT mode (first entry), or the
631
- * bare base bag for a single-mode rung.
632
- *
633
- * FAIL-LOUD: an undeclared `modeId` THROWS, naming the rung and its declared
634
- * list. There is deliberately NO silent default-to-first for an unknown id — a
635
- * stale mode id must surface, not quietly load the wrong scene.
636
- *
637
- * ── Layer 2: physics behavior ────────────────────────────────────────────────
638
- * A renderState carries ZERO physics: it selects the renderer, the seed and the camera.
639
- * The SCENE's world (and so its dimensionality) is its own co-init call, not here.
640
- *
641
- * @param {object} rung — a WALKTHROUGH entry.
642
- * @param {string|null} [modeId] — a declared mode id, or null for the default.
643
- * @returns {object} the normalized renderState bag.
644
- */
645
- export function effectiveRenderState(rung, modeId) {
646
- var modes = walkthroughModes(rung);
647
- var entry = null;
648
- if (modeId === undefined || modeId === null) {
649
- entry = modes.length > 0 ? modes[0] : null;
650
- } else {
651
- for (var i = 0; i < modes.length; i++) {
652
- if (modes[i].id === modeId) { entry = modes[i]; break; }
653
- }
654
- if (!entry) {
655
- throw new Error('[walkthrough] rung "' + (rung && rung.id) + '" has no mode "' +
656
- modeId + '" — declared modes: [' +
657
- modes.map(function (m) { return m.id; }).join(', ') +
658
- ']. (FAIL-LOUD.)');
659
- }
660
- }
661
- var base = (rung && rung.renderState) || {};
662
- var delta = (entry && entry.renderState) || {};
663
- var merged = Object.assign({}, base, delta);
664
- // `render` is an ORDERED apply-list, not a scalar: concat rather than replace.
665
- var baseRender = Array.isArray(base.render) ? base.render : [];
666
- var deltaRender = Array.isArray(delta.render) ? delta.render : [];
667
- if (baseRender.length || deltaRender.length) {
668
- merged.render = baseRender.concat(deltaRender);
669
- }
670
- return normalizeSavedRenderState(merged);
671
- }
672
-
673
- // ── First contact ────────────────────────────────────────────────────────────
674
-
675
- /**
676
- * FIRST_CONTACT_WALKTHROUGH_ID — the rung STUDIO opens on with no deep link.
677
- *
678
- * ── Layer 1: API contract ────────────────────────────────────────────────────
679
- * The ONE authority for "what does a newcomer see when they open the IDE". It is
680
- * a `walkthrough:<id>` selection value, so `AuthorView` loads it through the SAME
681
- * dropdown path a user click takes — no second boot branch, no template id to keep
682
- * in sync. Validated at module load (below): a typo fails loud immediately rather
683
- * than silently landing on a blank canvas.
684
- *
685
- * ── Layer 2: what it shows ───────────────────────────────────────────────────
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.
693
- *
694
- * @type {string}
695
- */
696
- export var FIRST_CONTACT_WALKTHROUGH_ID = 'cl1d-mirror-route';
697
-
698
- /**
699
- * walkthroughById(id) — id → rung, or null. The ONE resolver every consumer of a
700
- * declared rung id (first contact, a deep link) ferries instead of
701
- * re-writing `WALKTHROUGH.find(...)` at each call site.
702
- * @param {string} id
703
- * @returns {object|null}
704
- */
705
- export function walkthroughById(id) {
706
- for (var i = 0; i < WALKTHROUGH.length; i++) {
707
- if (WALKTHROUGH[i].id === id) { return WALKTHROUGH[i]; }
708
- }
709
- return null;
710
- }
711
-
712
- // FAIL-LOUD at module load for EVERY declared-id constant: a retirement that
713
- // orphans one of these must break here, loudly, at import time — never silently
714
- // downstream where a dead id turns into a blank stage or a self-embed.
715
- [['FIRST_CONTACT_WALKTHROUGH_ID', FIRST_CONTACT_WALKTHROUGH_ID]
716
- ].forEach(function (pair) {
717
- if (!walkthroughById(pair[1])) {
718
- throw new Error('[walkthrough] ' + pair[0] + ' "' + pair[1] +
719
- '" is not a declared rung — declared ids: [' +
720
- WALKTHROUGH.map(function (r) { return r.id; }).join(', ') + ']. (FAIL-LOUD.)');
721
- }
722
- });
723
-
724
- // ── is3D normalization (E-W0a; mode-aware S3/W2) ─────────────────────────────
725
- // STUDIO reads ONE `is3D` field for a selection. A rung's
726
- // 3D-ness is ALREADY declared by its (possibly mode-merged) `renderState.renderer` —
727
- // three IS the 3D renderer, the ONE display fact the load path threads — so `is3D` here is
728
- // DERIVED through `effectiveRenderState` for the rung's DEFAULT mode, never a
729
- // hand-typed second source that could drift (schema-mirror kill). Stamped once at
730
- // module load so every consumer sees the uniform field.
731
- WALKTHROUGH.forEach(function (rung) {
732
- rung.is3D = effectiveRenderState(rung, null).renderer === 'three';
733
- });
734
-
735
- /**
736
- * walkthroughIs3D(id, modeId) — the ONE mount-time 3D predicate for a walkthrough
737
- * rung. Derived through
738
- * `effectiveRenderState` (the single derivation point) for the given mode, or the
739
- * rung's default mode when `modeId` is omitted. Returns false for an unknown id;
740
- * THROWS (via effectiveRenderState) for a known id with an undeclared mode.
741
- * @param {string} id — a rung id (the `walkthrough:<id>` selection value).
742
- * @param {string|null} [modeId] — a declared mode id, or null for the default.
743
- * @returns {boolean}
744
- */
745
- export function walkthroughIs3D(id, modeId) {
746
- for (var i = 0; i < WALKTHROUGH.length; i++) {
747
- if (WALKTHROUGH[i].id === id) {
748
- return effectiveRenderState(WALKTHROUGH[i], modeId).renderer === 'three';
749
- }
750
- }
751
- return false;
752
- }