@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.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 (217) hide show
  1. package/CLAUDE.md +87 -0
  2. package/README.md +84 -2
  3. package/dist/dist-manifest.json +218 -0
  4. package/dist/eyes/index.js +109 -0
  5. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  6. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  7. package/dist/fat/pkg-node/package.json +12 -0
  8. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  9. package/dist/frqtl/compat/boot-node.js +34 -0
  10. package/dist/frqtl/compat/engine.js +127 -0
  11. package/dist/frqtl/compat/package-root.js +24 -0
  12. package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
  13. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  14. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  15. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  16. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  17. package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
  18. package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
  19. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  20. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  21. package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
  22. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  23. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  24. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  25. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  26. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  27. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  28. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  30. package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
  31. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  32. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  33. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  34. package/dist/frqtl/compat/vendor.js +196 -0
  35. package/dist/frqtl/node-entry.js +17 -0
  36. package/dist/frqtl/objects/FrqtlApp.js +2922 -0
  37. package/dist/frqtl/objects/ObjectFactory.js +297 -0
  38. package/dist/frqtl/objects/RecipeContext.js +322 -0
  39. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  40. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  41. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  42. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  43. package/dist/frqtl/objects/instruments/capture.js +711 -0
  44. package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
  45. package/dist/frqtl/objects/kinds-entry.js +14 -0
  46. package/dist/frqtl/objects/kinds.js +190 -0
  47. package/dist/frqtl/objects/objects/Box.js +273 -0
  48. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  49. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  50. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  51. package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
  52. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  53. package/dist/frqtl/objects/objects/LightSource.js +354 -0
  54. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  55. package/dist/frqtl/objects/objects/Tube.js +471 -0
  56. package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
  57. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  58. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  59. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  60. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  61. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  62. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  66. package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
  67. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  68. package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
  69. package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
  70. package/dist/frqtl/objects/objects/tagger.js +228 -0
  71. package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
  72. package/dist/frqtl/objects/schema/index.js +256 -0
  73. package/dist/frqtl/objects/schema/scopes.js +457 -0
  74. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  75. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  76. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  77. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  78. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  79. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  80. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  81. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  82. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
  83. package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  85. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  86. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  87. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  88. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  89. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  90. package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
  91. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  92. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  94. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
  95. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  96. package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
  97. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  98. package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
  99. package/dist/frqtl/objects/v1-replacements.js +30 -0
  100. package/dist/frqtl/package-core.js +23 -0
  101. package/dist/frqtl/parts/CameraControls.js +643 -0
  102. package/dist/frqtl/parts/CameraPath.js +575 -0
  103. package/dist/frqtl/parts/CollisionActions.js +35 -0
  104. package/dist/frqtl/parts/CollisionTypes.js +31 -0
  105. package/dist/frqtl/parts/DetectorWall.js +496 -0
  106. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  107. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  108. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  109. package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
  110. package/dist/frqtl/parts/LargeObject.js +406 -0
  111. package/dist/frqtl/parts/LightColor.js +255 -0
  112. package/dist/frqtl/parts/LiveChart.js +285 -0
  113. package/dist/frqtl/parts/LogicGate.js +720 -0
  114. package/dist/frqtl/parts/OvenBox.js +612 -0
  115. package/dist/frqtl/parts/PointSource.js +763 -0
  116. package/dist/frqtl/parts/Rectangle.js +200 -0
  117. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  118. package/dist/frqtl/parts/SlitScreen.js +628 -0
  119. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  120. package/dist/frqtl/parts/Trace.js +508 -0
  121. package/dist/frqtl/parts/Tunnel.js +1057 -0
  122. package/dist/frqtl/parts/Wall.js +1211 -0
  123. package/dist/frqtl/parts/index.js +33 -0
  124. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  126. package/dist/frqtl/scenes/atomic-model.js +88 -0
  127. package/dist/frqtl/scenes/big-bang.js +46 -0
  128. package/dist/frqtl/scenes/blackbody.js +81 -0
  129. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  130. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  131. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  132. package/dist/frqtl/scenes/counting-wall.js +38 -0
  133. package/dist/frqtl/scenes/double-slit.js +31 -0
  134. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  135. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  136. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  137. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
  138. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  139. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  140. package/dist/frqtl/scenes/empty.js +14 -0
  141. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  142. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  143. package/dist/frqtl/scenes/mirror-route.js +140 -0
  144. package/dist/frqtl/scenes/mirror.js +38 -0
  145. package/dist/frqtl/scenes/omni-3d.js +27 -0
  146. package/dist/frqtl/scenes/omni-source.js +22 -0
  147. package/dist/frqtl/scenes/self-interference.js +39 -0
  148. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  149. package/dist/frqtl/scenes/straight-walk.js +22 -0
  150. package/dist/frqtl/scenes/sun.js +39 -0
  151. package/dist/frqtl/scenes/two-source.js +37 -0
  152. package/dist/frqtl/scenes/wave.js +26 -0
  153. package/dist/shim/bundle/frqnb.js +112 -0
  154. package/dist/shim/bundle/index.js +121 -0
  155. package/dist/shim/bundle/indexHtml.js +107 -0
  156. package/dist/shim/bundle/manifest.js +95 -0
  157. package/dist/shim/bundle/zip.js +243 -0
  158. package/dist/shim/bus.js +67 -0
  159. package/dist/shim/controls/index.js +627 -0
  160. package/dist/shim/controls/registry.js +274 -0
  161. package/dist/shim/engine/artifact-shell.js +212 -0
  162. package/dist/shim/engine/boot-phases.js +44 -0
  163. package/dist/shim/engine/engine-stack-boot.js +59 -0
  164. package/dist/shim/engine/router.js +692 -0
  165. package/dist/shim/experiment-frame/index.js +296 -0
  166. package/dist/shim/expr/ast-schema.js +369 -0
  167. package/dist/shim/field-descriptor/index.js +326 -0
  168. package/dist/shim/frame-data/index.js +224 -0
  169. package/dist/shim/index.js +67 -0
  170. package/dist/shim/notebook/canvas-guard.js +82 -0
  171. package/dist/shim/notebook/catalog.js +35 -0
  172. package/dist/shim/notebook/format.js +736 -0
  173. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  174. package/dist/shim/notebook/kernel.js +672 -0
  175. package/dist/shim/notebook/knob.js +151 -0
  176. package/dist/shim/notebook/module-resolver.js +260 -0
  177. package/dist/shim/notebook/notebook-knobs.js +65 -0
  178. package/dist/shim/notebook/produced-bindings.js +67 -0
  179. package/dist/shim/notebook/realm-registry.js +121 -0
  180. package/dist/shim/notebook/realm-transport.js +73 -0
  181. package/dist/shim/notebook/setup-builder.js +510 -0
  182. package/dist/shim/notebook/store.js +159 -0
  183. package/dist/shim/notebook/vendor-importmap.js +134 -0
  184. package/dist/shim/notebook/view-builder.js +189 -0
  185. package/dist/shim/notebook/view-ctx.js +100 -0
  186. package/dist/shim/notebook/view-handle.js +202 -0
  187. package/dist/shim/notebook/warmup.js +102 -0
  188. package/dist/shim/package.json +4 -0
  189. package/dist/shim/page-params/index.js +214 -0
  190. package/dist/shim/project/identity.js +112 -0
  191. package/dist/shim/project/index.js +19 -0
  192. package/dist/shim/project/link.js +402 -0
  193. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  194. package/dist/shim/project/register-node.mjs +17 -0
  195. package/dist/shim/project/specifiers.js +106 -0
  196. package/dist/shim/registry/copy-gate.js +110 -0
  197. package/dist/shim/registry/exhibits.js +455 -0
  198. package/dist/shim/registry/experiments.js +215 -0
  199. package/dist/shim/registry/index.js +19 -0
  200. package/dist/shim/registry/scene-source.js +157 -0
  201. package/dist/shim/registry/walkthrough.js +807 -0
  202. package/dist/shim/render/capture-envelope.js +279 -0
  203. package/dist/shim/render/heat-colormap.js +92 -0
  204. package/dist/shim/render/render-stages.js +855 -0
  205. package/dist/shim/render/sun-look.js +33 -0
  206. package/dist/shim/render-state/apply.js +262 -0
  207. package/dist/shim/render-state/index.js +74 -0
  208. package/dist/shim/rpc/index.js +207 -0
  209. package/dist/shim/runtime/index.js +1023 -0
  210. package/dist/shim/storage/boot.js +69 -0
  211. package/dist/shim/storage/index.js +169 -0
  212. package/dist/shim/storage/local-storage-provider.js +230 -0
  213. package/dist/shim/storage/make-namespace-store.js +132 -0
  214. package/dist/shim/storage/modules.js +249 -0
  215. package/dist/shim/storage/projects.js +150 -0
  216. package/llms.txt +12 -0
  217. package/package.json +62 -4
@@ -0,0 +1,447 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupCircuitBasic — Engine-agnostic basic circuit experiment factory.
5
+ * Battery -> wire -> probe A -> resistor -> probe B -> sink.
6
+ *
7
+ * Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
8
+ */
9
+
10
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
11
+ import { LargeObject } from '../../parts/LargeObject.js';
12
+ import { QuantumEmitter } from '../objects/QuantumEmitter.js';
13
+ import { CircuitProbe } from '../objects/CircuitProbe.js';
14
+ import { CollisionTypes } from '../../parts/CollisionTypes.js';
15
+ import { CollisionActions } from '../../parts/CollisionActions.js';
16
+
17
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
18
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
19
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
20
+
21
+ // ── Imports ─────────────────────────────────────────────────────────
22
+
23
+ // ── Helpers ──────────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Create a horizontal or vertical wire (two parallel walls).
27
+ * Returns array of LargeObject instances.
28
+ */
29
+ function createWire(universe, x1, y1, x2, y2, width, wallThickness, color) {
30
+ color = color || [80, 80, 80];
31
+ var walls = [];
32
+ var dx = x2 - x1;
33
+ var dy = y2 - y1;
34
+ var isHorizontal = Math.abs(dx) >= Math.abs(dy);
35
+
36
+ if (isHorizontal) {
37
+ var left = Math.min(x1, x2);
38
+ var right = Math.max(x1, x2);
39
+ var centerY = (y1 + y2) / 2;
40
+ var segLen = right - left;
41
+
42
+ // Top wall
43
+ walls.push(new LargeObject(
44
+ universe,
45
+ [left, centerY - width / 2 - wallThickness, segLen, wallThickness],
46
+ CollisionTypes.WALL, [], 'TOP', 'bottom', null, 1, color
47
+ ));
48
+ // Bottom wall
49
+ walls.push(new LargeObject(
50
+ universe,
51
+ [left, centerY + width / 2, segLen, wallThickness],
52
+ CollisionTypes.WALL, [], 'BOTTOM', 'top', null, 1, color
53
+ ));
54
+ } else {
55
+ var top = Math.min(y1, y2);
56
+ var bottom = Math.max(y1, y2);
57
+ var centerX = (x1 + x2) / 2;
58
+ var segLen2 = bottom - top;
59
+
60
+ // Left wall
61
+ walls.push(new LargeObject(
62
+ universe,
63
+ [centerX - width / 2 - wallThickness, top, wallThickness, segLen2],
64
+ CollisionTypes.WALL, [], 'LEFT', 'right', null, 1, color
65
+ ));
66
+ // Right wall
67
+ walls.push(new LargeObject(
68
+ universe,
69
+ [centerX + width / 2, top, wallThickness, segLen2],
70
+ CollisionTypes.WALL, [], 'RIGHT', 'left', null, 1, color
71
+ ));
72
+ }
73
+ return walls;
74
+ }
75
+
76
+ /**
77
+ * Create a battery (source emitter + sink absorber + seal walls).
78
+ * Returns { emitter, sink, sourceMarker, sealWalls, setAmperage, setVoltage, setEnabled, isEnabled }.
79
+ */
80
+ function createBattery(universe, sourceX, sourceY, sinkX, sinkY, amperage, terminalSize, fundamentalDim, voltage, channelWidth, wallThickness) {
81
+ var sourceColor = [255, 60, 60];
82
+ var sinkColor = [60, 60, 255];
83
+ var wallColor = [80, 80, 80];
84
+
85
+ // Compute velocity direction
86
+ var dx = sinkX - sourceX;
87
+ var dy = sinkY - sourceY;
88
+ var dist = Math.sqrt(dx * dx + dy * dy) || 1;
89
+ var vx = (dx / dist) * voltage;
90
+ var vy = (dy / dist) * voltage;
91
+
92
+ // Source emitter
93
+ var sourceRect = [
94
+ sourceX - terminalSize / 2, sourceY - terminalSize / 2,
95
+ terminalSize, terminalSize
96
+ ];
97
+
98
+ var emitter = new QuantumEmitter(
99
+ universe,
100
+ [sourceRect],
101
+ vx, vy,
102
+ amperage,
103
+ null, // random charge
104
+ true, // auto_fire
105
+ false, // directional, not random
106
+ Infinity,
107
+ 0
108
+ );
109
+
110
+ // Sink (absorber) — must span the FULL sealed channel cross-section.
111
+ // A terminal-sized (8px) absorber inside a 10px channel let edge quanta
112
+ // slip past it into the end-cap wall behind it and bounce back, filling
113
+ // the right of the wire and building back-pressure across the resistor.
114
+ // Sizing the absorber across the whole channel (oriented perpendicular to
115
+ // flow) guarantees every quantum that reaches the terminal is removed.
116
+ var sinkIsHorizontal = Math.abs(dx) >= Math.abs(dy);
117
+ var sinkCross = channelWidth > 0 ? channelWidth + 2 * wallThickness : terminalSize;
118
+ var sinkRect = sinkIsHorizontal
119
+ ? [sinkX - terminalSize / 2, sinkY - sinkCross / 2, terminalSize, sinkCross]
120
+ : [sinkX - sinkCross / 2, sinkY - terminalSize / 2, sinkCross, terminalSize];
121
+ var sink = new LargeObject(
122
+ universe,
123
+ sinkRect,
124
+ CollisionTypes.NONE, [CollisionActions.ABSORB], 'battery_sink', 'closest', null, 1, sinkColor
125
+ );
126
+
127
+ // Source visual marker
128
+ var sourceMarker = new LargeObject(
129
+ universe,
130
+ [sourceX - terminalSize / 2, sourceY - terminalSize / 2, terminalSize, terminalSize],
131
+ CollisionTypes.NONE, [], 'battery_source_marker', 'closest', null, 1, sourceColor
132
+ );
133
+
134
+ // Seal walls
135
+ var sealWalls = [];
136
+ if (channelWidth > 0) {
137
+ var isHorizontal = Math.abs(dx) >= Math.abs(dy);
138
+ var cw = channelWidth;
139
+ var wt = wallThickness;
140
+ var ts = terminalSize;
141
+
142
+ if (isHorizontal) {
143
+ var flowRight = dx > 0;
144
+
145
+ // Source end-cap
146
+ var capX = flowRight ? sourceX - ts / 2 - wt : sourceX + ts / 2;
147
+ sealWalls.push(new LargeObject(
148
+ universe,
149
+ [capX, sourceY - cw / 2 - wt, wt, cw + 2 * wt],
150
+ CollisionTypes.WALL, [], flowRight ? 'LEFT' : 'RIGHT', flowRight ? 'right' : 'left', null, 1, wallColor
151
+ ));
152
+
153
+ // Source top/bottom walls
154
+ var zoneLeft = sourceX - ts / 2;
155
+ sealWalls.push(new LargeObject(
156
+ universe, [zoneLeft, sourceY - cw / 2 - wt, ts, wt],
157
+ CollisionTypes.WALL, [], 'TOP', 'bottom', null, 1, wallColor
158
+ ));
159
+ sealWalls.push(new LargeObject(
160
+ universe, [zoneLeft, sourceY + cw / 2, ts, wt],
161
+ CollisionTypes.WALL, [], 'BOTTOM', 'top', null, 1, wallColor
162
+ ));
163
+
164
+ // Sink end-cap
165
+ var sinkCapX = flowRight ? sinkX + ts / 2 : sinkX - ts / 2 - wt;
166
+ sealWalls.push(new LargeObject(
167
+ universe,
168
+ [sinkCapX, sinkY - cw / 2 - wt, wt, cw + 2 * wt],
169
+ CollisionTypes.WALL, [], flowRight ? 'RIGHT' : 'LEFT', flowRight ? 'left' : 'right', null, 1, wallColor
170
+ ));
171
+
172
+ // Sink top/bottom walls
173
+ var sinkZoneLeft = sinkX - ts / 2;
174
+ sealWalls.push(new LargeObject(
175
+ universe, [sinkZoneLeft, sinkY - cw / 2 - wt, ts, wt],
176
+ CollisionTypes.WALL, [], 'TOP', 'bottom', null, 1, wallColor
177
+ ));
178
+ sealWalls.push(new LargeObject(
179
+ universe, [sinkZoneLeft, sinkY + cw / 2, ts, wt],
180
+ CollisionTypes.WALL, [], 'BOTTOM', 'top', null, 1, wallColor
181
+ ));
182
+ } else {
183
+ var flowDown = dy > 0;
184
+
185
+ // Source end-cap
186
+ var capY = flowDown ? sourceY - ts / 2 - wt : sourceY + ts / 2;
187
+ sealWalls.push(new LargeObject(
188
+ universe,
189
+ [sourceX - cw / 2 - wt, capY, cw + 2 * wt, wt],
190
+ CollisionTypes.WALL, [], flowDown ? 'TOP' : 'BOTTOM', flowDown ? 'bottom' : 'top', null, 1, wallColor
191
+ ));
192
+
193
+ // Source left/right walls
194
+ var zoneTop = sourceY - ts / 2;
195
+ sealWalls.push(new LargeObject(
196
+ universe, [sourceX - cw / 2 - wt, zoneTop, wt, ts],
197
+ CollisionTypes.WALL, [], 'LEFT', 'right', null, 1, wallColor
198
+ ));
199
+ sealWalls.push(new LargeObject(
200
+ universe, [sourceX + cw / 2, zoneTop, wt, ts],
201
+ CollisionTypes.WALL, [], 'RIGHT', 'left', null, 1, wallColor
202
+ ));
203
+
204
+ // Sink end-cap
205
+ var sinkCapY = flowDown ? sinkY + ts / 2 : sinkY - ts / 2 - wt;
206
+ sealWalls.push(new LargeObject(
207
+ universe,
208
+ [sourceX - cw / 2 - wt, sinkCapY, cw + 2 * wt, wt],
209
+ CollisionTypes.WALL, [], flowDown ? 'BOTTOM' : 'TOP', flowDown ? 'top' : 'bottom', null, 1, wallColor
210
+ ));
211
+
212
+ // Sink left/right walls
213
+ var sinkZoneTop = sinkY - ts / 2;
214
+ sealWalls.push(new LargeObject(
215
+ universe, [sinkX - cw / 2 - wt, sinkZoneTop, wt, ts],
216
+ CollisionTypes.WALL, [], 'LEFT', 'right', null, 1, wallColor
217
+ ));
218
+ sealWalls.push(new LargeObject(
219
+ universe, [sinkX + cw / 2, sinkZoneTop, wt, ts],
220
+ CollisionTypes.WALL, [], 'RIGHT', 'left', null, 1, wallColor
221
+ ));
222
+ }
223
+ }
224
+
225
+ var currentAmperage = amperage;
226
+ var currentVoltage = voltage;
227
+ var enabled = true;
228
+
229
+ var batteryObj = {
230
+ emitter: emitter,
231
+ sink: sink,
232
+ sourceMarker: sourceMarker,
233
+ sealWalls: sealWalls,
234
+ enabled: enabled,
235
+ setAmperage: function (amp) {
236
+ currentAmperage = amp;
237
+ emitter.setBurstSize(amp);
238
+ },
239
+ setVoltage: function (v) {
240
+ currentVoltage = v;
241
+ var ndx = sinkX - sourceX;
242
+ var ndy = sinkY - sourceY;
243
+ var ndist = Math.sqrt(ndx * ndx + ndy * ndy) || 1;
244
+ emitter.setVelocity((ndx / ndist) * v, (ndy / ndist) * v);
245
+ },
246
+ setEnabled: function (en) {
247
+ enabled = en;
248
+ batteryObj.enabled = en;
249
+ emitter.setActive(en);
250
+ },
251
+ isEnabled: function () { return enabled; }
252
+ };
253
+ return batteryObj;
254
+ }
255
+
256
+ /**
257
+ * Create a resistor (constriction walls).
258
+ */
259
+ function createResistor(universe, x, y, length, channelWidth, outerWidth, horizontal, wallThickness, color) {
260
+ color = color || [140, 100, 50];
261
+ var walls = [];
262
+ if (horizontal) {
263
+ var left = x - length / 2;
264
+ var topWallH = (outerWidth - channelWidth) / 2;
265
+ walls.push(new LargeObject(
266
+ universe,
267
+ [left, y - outerWidth / 2, length, topWallH],
268
+ CollisionTypes.WALL, [], 'TOP', 'bottom', null, 1, color
269
+ ));
270
+ walls.push(new LargeObject(
271
+ universe,
272
+ [left, y + channelWidth / 2, length, topWallH],
273
+ CollisionTypes.WALL, [], 'BOTTOM', 'top', null, 1, color
274
+ ));
275
+ } else {
276
+ var top2 = y - length / 2;
277
+ var leftWallW = (outerWidth - channelWidth) / 2;
278
+ walls.push(new LargeObject(
279
+ universe,
280
+ [x - outerWidth / 2, top2, leftWallW, length],
281
+ CollisionTypes.WALL, [], 'LEFT', 'right', null, 1, color
282
+ ));
283
+ walls.push(new LargeObject(
284
+ universe,
285
+ [x + channelWidth / 2, top2, leftWallW, length],
286
+ CollisionTypes.WALL, [], 'RIGHT', 'left', null, 1, color
287
+ ));
288
+ }
289
+ return walls;
290
+ }
291
+
292
+ // ── Setup Factory ───────────────────────────────────────────────────
293
+
294
+ /**
295
+ * Create a basic circuit experiment: battery -> wire -> probeA -> resistor -> probeB -> sink.
296
+ *
297
+ * @param {Object} universe - The engine universe instance (JS or WASM), not yet init'd
298
+ * @param {Object} [options] - Configuration overrides
299
+ * @param {number} [options.wireWidth=10] - Wire channel width
300
+ * @param {number} [options.amperage=1] - Emission rate (particles per tick)
301
+ * @param {number} [options.voltage=10] - Emission velocity
302
+ * @param {number} [options.resistorWidth=4] - Resistor channel width
303
+ * @returns {Object} { battery, probes, largeObjects }
304
+ */
305
+ /**
306
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
307
+ * Exported as `setupCircuitBasic.OPTIONS` so a composing caller consumes it instead of
308
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
309
+ * read-set by static trace and fails if this list drifts from the code.
310
+ */
311
+ var OPTIONS = [
312
+ 'amperage', 'emissionRate', 'resistorWidth', 'voltage',
313
+ 'wireWidth',
314
+ ];
315
+
316
+ function setupCircuitBasic(universe, options) {
317
+ options = _assertSetupOptions('setupCircuitBasic', options, OPTIONS);
318
+
319
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
320
+ var canvas_rect = universe.universe_rect;
321
+ _requireWorldRect('setupCircuitBasic universe.universe_rect', canvas_rect);
322
+
323
+ var wireWidth = options.wireWidth || 10;
324
+ // 1 amp now means 10 quanta/tick (was 1). More emitted current → real
325
+ // crowding/back-pressure at the resistor gap instead of a thin trickle.
326
+ var QUANTA_PER_AMP = 10;
327
+ var amperage = (options.amperage || options.emissionRate || 1) * QUANTA_PER_AMP;
328
+ var voltage = options.voltage || 10;
329
+ var resistorWidth = options.resistorWidth || 4;
330
+ var fundamentalDim = 0;
331
+ var wallThickness = 3;
332
+
333
+ // Init universe: ground-up mode with brownian motion
334
+ universe.init(fundamentalDim, { wavelengthScale: 1 });
335
+ universe.brownianMotion = true;
336
+ universe.withInterQuantumCollisions = true;
337
+
338
+ // Layout constants (matching WASM adapter exactly)
339
+ var margin = 40;
340
+ var centerY = Math.round(canvas_rect.h / 2);
341
+ var wireLeft = margin;
342
+ var wireRight = canvas_rect.w - margin;
343
+ var wireLen = wireRight - wireLeft;
344
+
345
+ var batSourceX = wireLeft + 10;
346
+ var probeAX = wireLeft + Math.round(wireLen * 0.3);
347
+ var resistorX = wireLeft + Math.round(wireLen * 0.5);
348
+ var probeBX = wireLeft + Math.round(wireLen * 0.7);
349
+ var batSinkX = wireRight - 10;
350
+
351
+ // Main wire tube
352
+ var wireWalls = createWire(universe, wireLeft, centerY, wireRight, centerY, wireWidth, wallThickness);
353
+
354
+ // Battery
355
+ var battery = createBattery(
356
+ universe,
357
+ batSourceX, centerY,
358
+ batSinkX, centerY,
359
+ amperage,
360
+ wireWidth - 2,
361
+ fundamentalDim,
362
+ voltage,
363
+ wireWidth,
364
+ wallThickness
365
+ );
366
+
367
+ // Probes
368
+ var probeA = new CircuitProbe(universe, probeAX, centerY, wireWidth, 'before_resistor');
369
+ var probeB = new CircuitProbe(universe, probeBX, centerY, wireWidth, 'after_resistor');
370
+
371
+ // Resistor
372
+ var resistorWalls = createResistor(
373
+ universe, resistorX, centerY, 20, resistorWidth, wireWidth, true, wallThickness
374
+ );
375
+
376
+ // Right-end drain (absorber). Quanta that have crossed the resistor are
377
+ // removed at the RIGHT END of the wire (full channel cross-section,
378
+ // extending to the true wire end past the terminal) rather than bouncing
379
+ // off the terminator and back-filling the wire. Registered for collisions
380
+ // but intentionally NOT rendered (kept invisible).
381
+ var drainLeft = wireLeft + Math.round(wireLen * 0.88);
382
+ var drainRight = wireRight;
383
+ var drainW = Math.max(1, drainRight - drainLeft);
384
+ var drain = new LargeObject(
385
+ universe,
386
+ [drainLeft, centerY - (wireWidth / 2 + wallThickness), drainW, wireWidth + 2 * wallThickness],
387
+ CollisionTypes.NONE, [CollisionActions.ABSORB], 'RIGHT_drain', 'closest', null, 1, [40, 40, 70]
388
+ );
389
+
390
+ // Evaluation tick function: sample probes each tick
391
+ universe.experimentTickFunctions.push(function () {
392
+ probeA.queryDensity();
393
+ probeB.queryDensity();
394
+ return [];
395
+ });
396
+
397
+ var gateThreshold = 3;
398
+
399
+ // Collect all large objects for rendering
400
+ var largeObjects = [];
401
+ largeObjects.push.apply(largeObjects, wireWalls);
402
+ largeObjects.push(battery.sink);
403
+ largeObjects.push(battery.sourceMarker);
404
+ largeObjects.push.apply(largeObjects, battery.sealWalls);
405
+ largeObjects.push.apply(largeObjects, resistorWalls);
406
+ largeObjects.push(probeA.marker);
407
+ largeObjects.push(probeB.marker);
408
+
409
+ return {
410
+ battery: battery,
411
+ drain: drain,
412
+ probes: { before_resistor: probeA, after_resistor: probeB },
413
+ wireWalls: wireWalls,
414
+ resistorWalls: resistorWalls,
415
+ setBatteryAmperage: function (amp) { battery.setAmperage(amp); },
416
+ setBatteryVoltage: function (v) { battery.setVoltage(v); },
417
+ getCircuitState: function () {
418
+ var state = {};
419
+ var probeMap = { before_resistor: probeA, after_resistor: probeB };
420
+ for (var name in probeMap) {
421
+ if (probeMap.hasOwnProperty(name)) {
422
+ var probe = probeMap[name];
423
+ state[name] = {
424
+ flowRate: probe.getFlowRate(),
425
+ lastCount: probe.getLastCount(),
426
+ isHigh: probe.isHigh(gateThreshold)
427
+ };
428
+ }
429
+ }
430
+ return state;
431
+ },
432
+ largeObjects: largeObjects
433
+ };
434
+ }
435
+
436
+ // ── Statics (reusable helpers) ──────────────────────────────────────
437
+
438
+ setupCircuitBasic.createWire = createWire;
439
+ setupCircuitBasic.createBattery = createBattery;
440
+ setupCircuitBasic.createResistor = createResistor;
441
+
442
+ // ── Export ───────────────────────────────────────────────────────────
443
+
444
+ setupCircuitBasic.OPTIONS = OPTIONS;
445
+
446
+ export { setupCircuitBasic, createWire, createBattery, createResistor };
447
+
@@ -0,0 +1,188 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * setupCollisionMatrix — density-sweep collision experiment.
5
+ *
6
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
7
+ * @param {Object} universe - WASM universe (wasmOnly experiment); init()'d here.
8
+ * @param {Object} [opts]
9
+ * @param {number} [opts.level=6] - Dimension of every body (2^level px voxel; 6 = 64px).
10
+ * @param {number} [opts.offset=0] - Transverse launch offset (px): A below / B above the lane.
11
+ * @param {number} [opts.angle=0] - Converging vy magnitude (the approach angle).
12
+ *
13
+ * @returns {{
14
+ * level: number, voxel: number, capacity: number, laneYs: number[], ax0: number, bx0: number,
15
+ * lanes: Array<{ name:string, label:string, aQid:number, bQid:number,
16
+ * mass:number, ax0:number, bx0:number, laneY:number }>,
17
+ * matrixControl: { getOffset, setOffset, getAngle, setAngle, reset }
18
+ * }}
19
+ *
20
+ * ── Layer 2: physics behavior (what this PROVES) ─────────────────────────────
21
+ * SEVEN horizontal lanes, each a SYMMETRIC pair of Dim-6 boxes (equal mass) launched head-on
22
+ * — A from the left (+x), B from the right (−x) — at speed_prob=1. Mass is swept on a LOG
23
+ * (powers-of-2) scale: 1, 8, 64, 512, 1024, 2048, 4096(=capacity), so density ρ = mass/capacity
24
+ * runs ≈0.0002 … 1.0 DOWN the lanes: SPARSE at the TOP, FULL at the BOTTOM. Bodies are
25
+ * COLORLESS; the ROYGBIV render scheme colours each by its fullness.
26
+ *
27
+ * withInterQuantumCollisions=true runs voxel_collision_pass — the TWO-BUCKET momentum exchange
28
+ * (docs/internal/physics-lab/sam_notes/On_Collisions.md §Two-Bucket): colliding boxes EXCHANGE momentum and
29
+ * CONSERVE it; the per-tick exchange ceiling carries occupancy (ρ_A·ρ_B), so the bounce TIMING
30
+ * emerges from density — rigid (full) lanes bounce fast and clean, soft (sparse) lanes bounce
31
+ * slowly. The sweep reads this spectrum at a glance. Cross-lane coupling is impossible: lanes are
32
+ * y-separated ≫ the voxel, so no two lanes share a collision-proximity bucket.
33
+ *
34
+ * activeAxes {x,y}: x carries the head-on approach; the live OFFSET/ANGLE controls (no reload) add
35
+ * a transverse launch offset + a converging vy so collisions go OFF-AXIS — exercising the engine's
36
+ * SEPARATE per-axis momentum buckets (inc_x vs inc_y). density-sweep × offset/angle = the "offset
37
+ * angle combinations". At offset=0, angle=0 it is clean head-on.
38
+ */
39
+
40
+ import { assertSetupOptions as _assertSetupOptions, requireWorldRect as _requireWorldRect } from './_setupOptions.js';
41
+ import { collisionMatrixLanes as _lanes } from './_collisionMatrixLanes.js';
42
+
43
+ // The ONE unknown-option-key validator (objects/setup/_setupOptions.js). Resolved
44
+ // LAZILY in the browser: the game layer defines window.assertSetupOptions before any
45
+ // page script runs, but reading it at eval time would bake in a load-order dependency.
46
+
47
+ /**
48
+ * Lane descriptor: each lane is a SYMMETRIC pair of bodies (both the same mass) at a fixed y.
49
+ * DENSITY is swept on a strict LOGARITHMIC (powers-of-2) scale from ρ≈0 (one Dim-0 child →
50
+ * near-total pass-through) to ρ=1 (full box → hard abut) — non-linear probabilistic
51
+ * effects (collision prob ~ density² with density=mass/capacity) read cleanly on a log scale
52
+ * (CEO 2026-06-29). speed_prob=1 on every body so ALL lanes approach at the same 1px/tick,
53
+ * isolating density→collision from the fullness-gate movement-speed effect (that is G1's job).
54
+ * For a 2D Dim-6 box capacity = 64×64 = 4096 = 2^12, so the ladder is 2^0…2^12. The table is
55
+ * `_collisionMatrixLanes.js` — shared with the 3D matrix, whose capacity is 64³ and whose
56
+ * masses therefore differ while the DENSITIES are identical.
57
+ */
58
+ /**
59
+ * OPTIONS — this factory's own option vocabulary: EXACTLY the keys it reads.
60
+ * Exported as `setupCollisionMatrix.OPTIONS` so a composing caller consumes it instead of
61
+ * retyping it. `setup-factory-option-vocabulary.test.mjs` re-derives the
62
+ * read-set by static trace and fails if this list drifts from the code.
63
+ */
64
+ var OPTIONS = [
65
+ 'angle', 'level',
66
+ 'offset',
67
+ ];
68
+
69
+ function setupCollisionMatrix(universe, opts) {
70
+ opts = _assertSetupOptions('setupCollisionMatrix', opts, OPTIONS);
71
+ var level = (opts.level !== undefined) ? opts.level : 6;
72
+
73
+ // The world rect the co-init call wrote (EGPTUniverse.create({rect})) — the ONE size.
74
+ var rect = universe.universe_rect;
75
+ var world = _requireWorldRect('setupCollisionMatrix universe.universe_rect', rect);
76
+ var cw = world.w, ch = world.h;
77
+
78
+ // activeAxes: BOTH x and y. x carries the head-on approach; y carries the OFFSET/ANGLE
79
+ // switch (transverse offset + converging vy) that exercises the engine's SEPARATE per-axis
80
+ // momentum buckets (inc_x vs inc_y) and the per-axis contact-cross-section ceiling. At
81
+ // offset=0, angle=0 the motion is pure-x head-on (vy=0); the transverse yo-yo adds only a
82
+ // small ±1px wiggle that does not cross the ~100px lane spacing over a collision.
83
+ universe.init(0, {
84
+ wavelengthScale: 1,
85
+ brownianMotion: false,
86
+ activeAxes: { x: true, y: true, z: false }
87
+ });
88
+ // Enable the voxel_collision_pass so same-dimension boxes exchange momentum.
89
+ universe.withInterQuantumCollisions = true;
90
+
91
+ universe.addDimension(level);
92
+ var voxel = Math.pow(2, level); // 64 for level=6
93
+ var half = voxel / 2; // makeLeafFrame takes TOP-LEFT; we want CENTRES.
94
+
95
+ // Canonical launch CENTRES (same for every lane). A left of centre, B right, 3 voxels apart.
96
+ var midX = Math.floor(cw * 0.5);
97
+ var ax0 = midX - Math.floor(1.5 * voxel); // ~254 centre for 700-wide canvas
98
+ var bx0 = midX + Math.floor(1.5 * voxel); // ~446 centre
99
+
100
+ // Lane y-CENTRES: N lanes evenly distributed (centres at ch*(2i+1)/(2N)). These are CENTRES
101
+ // — the boxes are placed top-left = centre − half, so a 64px box centred at the bottom lane
102
+ // (y≈650) spans 618..682 and stays IN BOUNDS (H=700). Passing the centre AS the top-left was
103
+ // the boundary-kill bug: the bottom lane ran off the edge and the boundary check zeroed it.
104
+ var N = _lanes.LANE_DENSITIES.length;
105
+ var laneYs = _lanes.LANE_DENSITIES.map(function (_, i) { return Math.round(ch * (2 * i + 1) / (2 * N)); });
106
+
107
+ // OFFSET / ANGLE state (the switch). offsetPx = transverse separation of the pair at launch
108
+ // (A below centre, B above → glancing); angleVy = converging y-velocity (A up, B down →
109
+ // both x AND y momentum at impact). Both default 0 = clean head-on.
110
+ var offsetPx = (opts.offset !== undefined) ? opts.offset : 0;
111
+ var angleVy = (opts.angle !== undefined) ? opts.angle : 0;
112
+
113
+ var capacity = null;
114
+
115
+ // Seat one body's position + velocity for the current offset/angle (CENTRE coords).
116
+ function seat(qid, cx, cy, dirX, mass) {
117
+ universe.setFrameProperty(qid, 'x', cx);
118
+ universe.setFrameProperty(qid, 'y', cy + dirX * offsetPx); // A(dirX=+1) below, B above
119
+ universe.setFrameProperty(qid, 'vx', dirX);
120
+ universe.setFrameProperty(qid, 'vy', -dirX * angleVy); // converge toward the lane
121
+ universe.setFrameProperty(qid, 'mass', mass);
122
+ universe.setFrameProperty(qid, 'speed_prob', 1.0);
123
+ }
124
+
125
+ // Build every body FIRST — the capacity that sets the lane ladder is whatever the
126
+ // ENGINE reports for the body it actually built, so the masses cannot be known
127
+ // until a body exists.
128
+ var built = [];
129
+ for (var i = 0; i < N; i++) {
130
+ var laneY = laneYs[i];
131
+ // Place COLORLESS boxes (ROYGBIV scheme drives colour from fullness) at top-left = centre−half.
132
+ var aHandle = universe.makeLeafFrame(ax0 - half, laneY - half, level, null, 1, 0, true, 0, null);
133
+ universe.injectFrame(aHandle);
134
+ var aQid = universe.getFrameQuantumId(aHandle);
135
+ var bHandle = universe.makeLeafFrame(bx0 - half, laneY - half, level, null, -1, 0, true, 0, null);
136
+ universe.injectFrame(bHandle);
137
+ var bQid = universe.getFrameQuantumId(bHandle);
138
+
139
+ if (capacity === null) { capacity = universe.getFrameProperty(aQid, 'mass'); }
140
+ built.push({ aQid: aQid, bQid: bQid });
141
+ }
142
+
143
+ var laneMasses = _lanes.laneMassesForCapacity(capacity);
144
+
145
+ var lanes = built.map(function (b, i) {
146
+ var laneY = laneYs[i];
147
+ var m = laneMasses[i];
148
+ // SYMMETRIC lane; seat applies mass + the current offset/angle.
149
+ seat(b.aQid, ax0, laneY, 1, m);
150
+ seat(b.bQid, bx0, laneY, -1, m);
151
+
152
+ return {
153
+ name: 'm=' + m,
154
+ label: 'mass ' + m + ' / ' + capacity + ' (density ' + (m / capacity).toPrecision(2) + ')',
155
+ aQid: b.aQid, bQid: b.bQid, mass: m, ax0: ax0, bx0: bx0, laneY: laneY
156
+ };
157
+ });
158
+
159
+ function reseatAll() {
160
+ lanes.forEach(function (lane) {
161
+ seat(lane.aQid, lane.ax0, lane.laneY, 1, lane.mass);
162
+ seat(lane.bQid, lane.bx0, lane.laneY, -1, lane.mass);
163
+ });
164
+ }
165
+
166
+ // ── Live control handle (Blackbody pattern — no reload) ──────────────────
167
+ // setOffset/setAngle change the transverse offset and converging angle and RE-SEAT every
168
+ // lane so the new geometry takes effect immediately; reset just re-seats at the current
169
+ // offset/angle. The density sweep (per lane) × the offset/angle slider = the "offset angle
170
+ // combinations" the matrix is meant to explore.
171
+ var matrixControl = {
172
+ getOffset: function () { return offsetPx; },
173
+ setOffset: function (v) { offsetPx = v; reseatAll(); },
174
+ getAngle: function () { return angleVy; },
175
+ setAngle: function (v) { angleVy = v; reseatAll(); },
176
+ reset: function () { reseatAll(); }
177
+ };
178
+
179
+ return {
180
+ level: level, voxel: voxel, capacity: capacity, laneYs: laneYs,
181
+ ax0: ax0, bx0: bx0, lanes: lanes, matrixControl: matrixControl
182
+ };
183
+ }
184
+
185
+ setupCollisionMatrix.OPTIONS = OPTIONS;
186
+
187
+ export { setupCollisionMatrix };
188
+