@descix/frqtl-node 0.0.0-stage → 0.6.0-p0.2

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 (218) hide show
  1. package/CLAUDE.md +87 -0
  2. package/LICENSE.md +664 -0
  3. package/README.md +84 -2
  4. package/dist/dist-manifest.json +218 -0
  5. package/dist/eyes/index.js +109 -0
  6. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  7. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  8. package/dist/fat/pkg-node/package.json +12 -0
  9. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  10. package/dist/frqtl/compat/boot-node.js +34 -0
  11. package/dist/frqtl/compat/engine.js +127 -0
  12. package/dist/frqtl/compat/package-root.js +24 -0
  13. package/dist/frqtl/compat/providers/wasm-engine.js +2499 -0
  14. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  15. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  16. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  17. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  18. package/dist/frqtl/compat/renderer/RenderFamily.js +84 -0
  19. package/dist/frqtl/compat/renderer/RenderMesh.js +99 -0
  20. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  21. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  22. package/dist/frqtl/compat/renderer/captureStill-common.js +258 -0
  23. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  24. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  25. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  26. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  27. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  28. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  30. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  31. package/dist/frqtl/compat/renderer/providers/three-provider.js +2470 -0
  32. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  33. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  34. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  35. package/dist/frqtl/compat/vendor.js +196 -0
  36. package/dist/frqtl/node-entry.js +17 -0
  37. package/dist/frqtl/objects/FrqtlApp.js +2920 -0
  38. package/dist/frqtl/objects/ObjectFactory.js +296 -0
  39. package/dist/frqtl/objects/RecipeContext.js +322 -0
  40. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  41. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  42. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  43. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  44. package/dist/frqtl/objects/instruments/capture.js +710 -0
  45. package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -0
  46. package/dist/frqtl/objects/kinds-entry.js +14 -0
  47. package/dist/frqtl/objects/kinds.js +190 -0
  48. package/dist/frqtl/objects/objects/Box.js +273 -0
  49. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  50. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  51. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  52. package/dist/frqtl/objects/objects/DirectedTunnel.js +411 -0
  53. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  54. package/dist/frqtl/objects/objects/LightSource.js +351 -0
  55. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  56. package/dist/frqtl/objects/objects/Tube.js +471 -0
  57. package/dist/frqtl/objects/objects/absorbRadiation.js +309 -0
  58. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  59. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  60. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  61. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  62. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  66. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  67. package/dist/frqtl/objects/objects/makeControlsForObject.js +820 -0
  68. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  69. package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
  70. package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
  71. package/dist/frqtl/objects/objects/tagger.js +228 -0
  72. package/dist/frqtl/objects/schema/engine-scopes.js +468 -0
  73. package/dist/frqtl/objects/schema/index.js +256 -0
  74. package/dist/frqtl/objects/schema/scopes.js +457 -0
  75. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  76. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  77. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  78. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  79. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  80. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  81. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  82. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  83. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +250 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -0
  85. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  86. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  87. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  88. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  89. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  90. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  91. package/dist/frqtl/objects/setup/setupLightSourceLab.js +277 -0
  92. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  94. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  95. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1258 -0
  96. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  97. package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
  98. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  99. package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
  100. package/dist/frqtl/objects/v1-replacements.js +29 -0
  101. package/dist/frqtl/package-core.js +23 -0
  102. package/dist/frqtl/parts/CameraControls.js +643 -0
  103. package/dist/frqtl/parts/CameraPath.js +575 -0
  104. package/dist/frqtl/parts/CollisionActions.js +34 -0
  105. package/dist/frqtl/parts/CollisionTypes.js +30 -0
  106. package/dist/frqtl/parts/DetectorWall.js +496 -0
  107. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  108. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  109. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  110. package/dist/frqtl/parts/GraphingDetectorWall.js +489 -0
  111. package/dist/frqtl/parts/LargeObject.js +405 -0
  112. package/dist/frqtl/parts/LightColor.js +255 -0
  113. package/dist/frqtl/parts/LiveChart.js +285 -0
  114. package/dist/frqtl/parts/LogicGate.js +717 -0
  115. package/dist/frqtl/parts/OvenBox.js +611 -0
  116. package/dist/frqtl/parts/PointSource.js +762 -0
  117. package/dist/frqtl/parts/Rectangle.js +200 -0
  118. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  119. package/dist/frqtl/parts/SlitScreen.js +628 -0
  120. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  121. package/dist/frqtl/parts/Trace.js +508 -0
  122. package/dist/frqtl/parts/Tunnel.js +1057 -0
  123. package/dist/frqtl/parts/Wall.js +1211 -0
  124. package/dist/frqtl/parts/index.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  126. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  127. package/dist/frqtl/scenes/atomic-model.js +88 -0
  128. package/dist/frqtl/scenes/big-bang.js +46 -0
  129. package/dist/frqtl/scenes/blackbody.js +81 -0
  130. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  131. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  132. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  133. package/dist/frqtl/scenes/counting-wall.js +38 -0
  134. package/dist/frqtl/scenes/double-slit.js +31 -0
  135. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  136. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  137. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  138. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +243 -0
  139. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  140. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  141. package/dist/frqtl/scenes/empty.js +14 -0
  142. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  143. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  144. package/dist/frqtl/scenes/mirror-route.js +140 -0
  145. package/dist/frqtl/scenes/mirror.js +38 -0
  146. package/dist/frqtl/scenes/omni-3d.js +27 -0
  147. package/dist/frqtl/scenes/omni-source.js +22 -0
  148. package/dist/frqtl/scenes/self-interference.js +39 -0
  149. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  150. package/dist/frqtl/scenes/straight-walk.js +22 -0
  151. package/dist/frqtl/scenes/sun.js +39 -0
  152. package/dist/frqtl/scenes/two-source.js +37 -0
  153. package/dist/frqtl/scenes/wave.js +26 -0
  154. package/dist/shim/bundle/frqnb.js +112 -0
  155. package/dist/shim/bundle/index.js +121 -0
  156. package/dist/shim/bundle/indexHtml.js +107 -0
  157. package/dist/shim/bundle/manifest.js +95 -0
  158. package/dist/shim/bundle/zip.js +243 -0
  159. package/dist/shim/bus.js +67 -0
  160. package/dist/shim/controls/index.js +627 -0
  161. package/dist/shim/controls/registry.js +274 -0
  162. package/dist/shim/engine/artifact-shell.js +211 -0
  163. package/dist/shim/engine/boot-phases.js +44 -0
  164. package/dist/shim/engine/engine-stack-boot.js +59 -0
  165. package/dist/shim/engine/router.js +690 -0
  166. package/dist/shim/experiment-frame/index.js +296 -0
  167. package/dist/shim/expr/ast-schema.js +369 -0
  168. package/dist/shim/field-descriptor/index.js +325 -0
  169. package/dist/shim/frame-data/index.js +224 -0
  170. package/dist/shim/index.js +67 -0
  171. package/dist/shim/notebook/canvas-guard.js +82 -0
  172. package/dist/shim/notebook/catalog.js +35 -0
  173. package/dist/shim/notebook/format.js +736 -0
  174. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  175. package/dist/shim/notebook/kernel.js +671 -0
  176. package/dist/shim/notebook/knob.js +150 -0
  177. package/dist/shim/notebook/module-resolver.js +260 -0
  178. package/dist/shim/notebook/notebook-knobs.js +65 -0
  179. package/dist/shim/notebook/produced-bindings.js +67 -0
  180. package/dist/shim/notebook/realm-registry.js +121 -0
  181. package/dist/shim/notebook/realm-transport.js +73 -0
  182. package/dist/shim/notebook/setup-builder.js +507 -0
  183. package/dist/shim/notebook/store.js +159 -0
  184. package/dist/shim/notebook/vendor-importmap.js +134 -0
  185. package/dist/shim/notebook/view-builder.js +189 -0
  186. package/dist/shim/notebook/view-ctx.js +100 -0
  187. package/dist/shim/notebook/view-handle.js +202 -0
  188. package/dist/shim/notebook/warmup.js +102 -0
  189. package/dist/shim/package.json +4 -0
  190. package/dist/shim/page-params/index.js +213 -0
  191. package/dist/shim/project/identity.js +112 -0
  192. package/dist/shim/project/index.js +19 -0
  193. package/dist/shim/project/link.js +402 -0
  194. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  195. package/dist/shim/project/register-node.mjs +17 -0
  196. package/dist/shim/project/specifiers.js +106 -0
  197. package/dist/shim/registry/copy-gate.js +110 -0
  198. package/dist/shim/registry/exhibits.js +455 -0
  199. package/dist/shim/registry/experiments.js +215 -0
  200. package/dist/shim/registry/index.js +19 -0
  201. package/dist/shim/registry/scene-source.js +157 -0
  202. package/dist/shim/registry/walkthrough.js +804 -0
  203. package/dist/shim/render/capture-envelope.js +279 -0
  204. package/dist/shim/render/heat-colormap.js +92 -0
  205. package/dist/shim/render/render-stages.js +855 -0
  206. package/dist/shim/render/sun-look.js +33 -0
  207. package/dist/shim/render-state/apply.js +262 -0
  208. package/dist/shim/render-state/index.js +74 -0
  209. package/dist/shim/rpc/index.js +207 -0
  210. package/dist/shim/runtime/index.js +1023 -0
  211. package/dist/shim/storage/boot.js +69 -0
  212. package/dist/shim/storage/index.js +169 -0
  213. package/dist/shim/storage/local-storage-provider.js +230 -0
  214. package/dist/shim/storage/make-namespace-store.js +132 -0
  215. package/dist/shim/storage/modules.js +249 -0
  216. package/dist/shim/storage/projects.js +150 -0
  217. package/llms.txt +12 -0
  218. package/package.json +63 -4
@@ -0,0 +1,496 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * DetectorWall — the provenance-splitting detector screen.
5
+ *
6
+ * ── A DetectorWall IS … (the datasheet line) ────────────────────────────────
7
+ *
8
+ * A DetectorWall is a **`Wall{detect}` + a `Tagger` read**: the Wall supplies the
9
+ * compiled op-program (`[COUNT, ABSORB]` + `collect_collided:true`, canonical bench
10
+ * LO7 MARK+COUNT) that counts and removes each arriving frame and buffers a FrameData
11
+ * copy of it; the Tagger supplies the read of the provenance string an UPSTREAM tagger
12
+ * (SlitScreen's slit LOs, whose `CollisionActions.MARK` is itself a Tagger write) put
13
+ * on that frame. Everything this file adds on top is bookkeeping: partition the hits
14
+ * by that tag, and keep two accumulators (the Y-marginal and the Y×Z grid) whose
15
+ * grid-add / marginal-derive helpers are the Wall atom's own statics.
16
+ *
17
+ * It owns NO op-program, NO string_map path and NO grid algebra of its own. Composed
18
+ * from — never wrapping — `Wall` (`./Wall.js`) and `Tagger` (`../objects/objects/tagger.js`).
19
+ *
20
+ * ── Layer 1: API contract ───────────────────────────────────────────────────
21
+ *
22
+ * - The inner Wall registers with `collect_collided:true` so the engine accumulates a
23
+ * point-in-time FrameData copy for every frame that collided this tick, AFTER the
24
+ * op-program (the upstream MARK wrote the slit name) and BEFORE Step-7 cleanup
25
+ * (ABSORB removes the frame).
26
+ * - `getCollisionHistogram(tickData)` reads this LO's collided frames from the
27
+ * marshalled tick output (`tickData.objects[handle].frames`) each tick and
28
+ * accumulates a running Map<y, count>, split by slit provenance read through
29
+ * `Tagger.readTag(fd)` (the canonical `detector_name` key — this file never names
30
+ * `data.string_map`).
31
+ *
32
+ * ── Layer 2: physics behavior ───────────────────────────────────────────────
33
+ *
34
+ * ZERO physics (SU1) beyond the Wall's own compiled program. Counting, tagging and
35
+ * reading move nothing: the canonical bench holds 28/28 across this composition.
36
+ *
37
+ * Engine-agnostic: the readout is identical on every engine (no capability gap).
38
+ */
39
+
40
+ import { LargeObject } from './LargeObject.js';
41
+ import { CollisionTypes } from './CollisionTypes.js';
42
+ import { CollisionActions } from './CollisionActions.js';
43
+ import { Wall } from './Wall.js';
44
+ import * as Tagger from '../objects/objects/tagger.js';
45
+ import { ObjectSchema } from '../objects/schema/index.js';
46
+ import { registerKind } from '../objects/kinds.js';
47
+
48
+ // ── Imports (Node.js) or globals (browser) ──────────────────────────
49
+
50
+ // ── DetectorWall ─────────────────────────────────────────────────────
51
+
52
+ /**
53
+ * Detector/absorber wall that counts arriving particles and removes them.
54
+ *
55
+ * Wraps a single {@link LargeObject} configured with `[COUNT, ABSORB]` actions
56
+ * and collision type `NONE`. The `ABSORB` action removes the particle so it
57
+ * does not pile up. The `COUNT` action increments the LO's internal counter.
58
+ *
59
+ * G1: the LO is registered with `collect_collided: true`. Each call to
60
+ * `getCollisionHistogram(tickData)` reads this LO's collided frames from the
61
+ * marshalled tick output (`tickData.objects[handle].frames`) and accumulates the
62
+ * running histogram (no per-LO collision query — the engine stays generic).
63
+ * Slit provenance is read through the `Tagger` part (`Tagger.readTag(fd)`) — the
64
+ * matching write is SlitScreen's slit LOs' MARK op-program (the same part), captured
65
+ * in the collided FrameData AFTER the op-program runs, before ABSORB kills the frame.
66
+ *
67
+ * ## Physics you can drive
68
+ *
69
+ * **`wallX`** — the x position of the detector. In `setupDoubleSlit` this is set
70
+ * to `slitScreen.left + slitScreen.width + 3 * wavelength`. Moving it closer to
71
+ * the slit screen (smaller `L`) compresses the fringe pattern vertically; moving
72
+ * it further (larger `L`) expands the fringe spacing. This is the `L` in the
73
+ * classical fringe-spacing formula `Δy = λL/d`.
74
+ *
75
+ * **Histogram readout** — call `detectorWall.getCollisionHistogram()` each tick
76
+ * to accumulate hits. Returns `{ combined: Map<y,count>, slit1: Map<y,count>,
77
+ * slit2: Map<y,count>, total: number }`. The accumulated `combined` map forms the
78
+ * detector interference pattern.
79
+ *
80
+ * **`wallWidth`** (default 20 px) — wider wall = more forgiving capture zone;
81
+ * narrower = sparser but more positionally precise hit data. Width does not affect
82
+ * fringe position, only total hit rate.
83
+ *
84
+ * @param {Object} universe - The engine universe instance (JS or WASM).
85
+ * @param {Object} canvasRect - The world rect `{left,top,w,h}` (or the rect tuple),
86
+ * read through `ObjectSchema.worldRect` — a missing or non-positive dimension THROWS.
87
+ * Used to derive default wall height and x position.
88
+ * @param {number} [wallX] - X position of the detector wall (physics px).
89
+ * Defaults to `canvasRect.w - 20` (near the right edge).
90
+ * @param {number} [wallY=0] - Y position (physics px).
91
+ * @param {string} [wallCollisionType] - RETIRED positional slot. The counting
92
+ * atom's collision mechanics are the compiled `destroy`+`count` op-program;
93
+ * there is no collision_type override. Passing anything other than `NONE`
94
+ * THROWS (FAIL-LOUD — never a silently ignored argument).
95
+ * @param {Array} [wallColor=[120,120,120]] - RGB color for rendering.
96
+ * @param {number} [wallHeight] - Wall height (physics px); defaults to
97
+ * `canvasRect.h`.
98
+ * @param {number} [wallWidth=20] - Wall thickness (physics px).
99
+ * @param {string} [wallName='detector_wall'] - Identifier used to key the
100
+ * collision histogram in the universe.
101
+ * @param {Object} [opts]
102
+ * @param {number} [opts.depth] - When provided, the detector LO is built as a
103
+ * 6-element `[left,top,z,w,h,d]` rect spanning the FULL z-extent
104
+ * `[-depth/2, +depth/2]` (Tube.js precedent) — without it, z-spread quanta
105
+ * (the emitter z-spread) fly past a paper-thin (z=0,d=w)
106
+ * detector. OMITTED (default) → legacy 4-element rect, byte-identical.
107
+ */
108
+
109
+ function DetectorWall(universe, canvasRect, wallX, wallY, wallCollisionType, wallColor, wallHeight, wallWidth, wallName, opts) {
110
+ opts = opts || {};
111
+ var depth = (opts.depth !== undefined && opts.depth !== null) ? opts.depth : null;
112
+
113
+ this.universe = universe;
114
+ this._schemaKind = 'DetectorWall';
115
+
116
+ // The world rect, through the ONE normalizer (a zero-high detector counts nothing
117
+ // and would report it as physics — so a missing dimension throws, never zeroes).
118
+ var world = ObjectSchema.worldRect(canvasRect, 'DetectorWall canvasRect');
119
+ var cw = world.w, ch = world.h;
120
+
121
+ // Defaults
122
+ wallX = (wallX !== null && wallX !== undefined) ? wallX : (cw - 20);
123
+ wallY = (wallY !== null && wallY !== undefined) ? wallY : 0;
124
+ // The collision_type override died with the legacy wall lexicon: the counting
125
+ // atom's mechanics ARE its op-program. A caller still naming a non-NONE type
126
+ // is asking for something that no longer exists — say so.
127
+ if (wallCollisionType !== null && wallCollisionType !== undefined &&
128
+ wallCollisionType !== CollisionTypes.NONE) {
129
+ throw new Error('[DetectorWall] the wallCollisionType override is retired ("' +
130
+ wallCollisionType + '") — the counting atom is Wall{collisionBehavior:' +
131
+ '\'destroy\', hooks:[\'count\']} and its mechanics are the compiled ' +
132
+ 'op-program. Pass NONE (or omit the argument).');
133
+ }
134
+ wallColor = wallColor || [120, 120, 120];
135
+ wallHeight = (wallHeight !== null && wallHeight !== undefined) ? wallHeight : ch;
136
+ wallWidth = (wallWidth !== null && wallWidth !== undefined) ? wallWidth : 20;
137
+ wallName = wallName || ('detector_wall' + Math.floor(Math.random() * 1000000));
138
+
139
+ this.wallX = wallX;
140
+ this._wallHeight = wallHeight;
141
+ this.depth = depth;
142
+
143
+ // G1: register with collect_collided:true so the engine populates the
144
+ // per-tick collided-frame buffer. The buffer is read by getCollisionHistogram()
145
+ // between ticks to accumulate the running histogram.
146
+ //
147
+ // NOTE: collect_collided is passed in the spec object that LargeObject forwards
148
+ // to universe.registerLargeObject(). It is a spec field (not a post-registration
149
+ // setter), so it is set atomically at registration time and never changes.
150
+ // 6-element full-z-depth rect when opts.depth is set
151
+ // (Tube.js precedent); legacy 4-element rect otherwise (byte-identical).
152
+ var wallRect = (depth !== null)
153
+ ? [wallX, wallY, -depth / 2, wallWidth, wallHeight, depth]
154
+ : [wallX, wallY, wallWidth, wallHeight];
155
+ // The detector's inner engine object IS the counting atom —
156
+ // `Wall{collisionBehavior:'destroy', hooks:['count']}` (= preset
157
+ // 'detector-screen'): kill the arrival, increment the wall's counter, buffer
158
+ // the collided FrameData. MEASURED equal to the legacy `detect` construction on
159
+ // the canonical WASM engine (same alive-trace tick-by-tick, same total); the two
160
+ // compiled programs carry the SAME two ops in the opposite order, and they
161
+ // commute (count reads only self.count, the kill writes only frame.is_alive).
162
+ // The rich histogram (grid + slit provenance + the #61 guard) stays OWNED here —
163
+ // this wrapper delegates only the plain-wall LO construction, not the counting.
164
+ this.name = wallName; // the declared name is the detector's own construction fact
165
+ this._innerWall = new Wall(universe, {
166
+ rect: wallRect,
167
+ collisionBehavior: 'destroy',
168
+ hooks: ['count'],
169
+ name: wallName,
170
+ color: wallColor
171
+ });
172
+ this.detector_wall = this._innerWall.lo;
173
+ // THE ONE UP-LINK: the detector composed this Wall (which stamps its own LO).
174
+ this._innerWall._owner = this;
175
+ // Render: the Tube.js precedent — a 6-element
176
+ // rect gives the ENGINE a real z-slab, but _buildLoMesh (three-provider.js)
177
+ // only builds a 3D BoxGeometry when the LO ALSO carries the render-only
178
+ // `shape='box'` + `depth` hint (see SlitScreen.js for the identical fix).
179
+ if (this.detector_wall.rect.d !== undefined) {
180
+ this.detector_wall.shape = 'box';
181
+ this.detector_wall.depth = this.detector_wall.rect.d;
182
+ }
183
+
184
+ // ── Accumulated histogram state (G1) ─────────────────────────────
185
+ // Running Maps: y → hit count. Accumulated across ticks (double-slit pattern
186
+ // is cumulative — many ticks of hits build the interference fringes). This is
187
+ // the Y-MARGINAL — unchanged code path, byte-identical to pre-B1 (the CLI,
188
+ // charts, and the committed golden all read exactly these fields).
189
+ this._combined = new Map(); // all slits combined
190
+ this._slit1 = new Map(); // hits tagged 'slit1' by SlitScreen MARK op-program
191
+ this._slit2 = new Map(); // hits tagged 'slit2' by SlitScreen MARK op-program
192
+ this._total = 0;
193
+ this._slit1Total = 0;
194
+ this._slit2Total = 0;
195
+
196
+ // ── Y×Z grid (§4.2) ───────────────────────────────────────────────
197
+ // Additive capture alongside the (untouched) Y-marginal above: Map<y, Map<z,count>>.
198
+ // In 2D (depth omitted), z is always 0 (fd.z defaults to 0 — see the loop below),
199
+ // so every grid entry has a single z=0 bucket and `DetectorWall.deriveMarginalY(grid)`
200
+ // reproduces `this._combined` exactly — the Y-marginal IS the derived marginal of
201
+ // this grid (verified by a unit test), even though the accumulator above is not
202
+ // rewritten to compute it lazily (kept untouched to protect the golden).
203
+ this._grid = new Map(); // all slits combined
204
+ this._gridSlit1 = new Map();
205
+ this._gridSlit2 = new Map();
206
+
207
+ // Throw-guard (THROW, never silent dedup) —
208
+ // the last TickData object consumed by getCollisionHistogram(). Applies in 2D too.
209
+ this._lastTickData = undefined;
210
+ }
211
+
212
+ /**
213
+ * Add one hit at (y,z) to a Y×Z grid (Map<y, Map<z,count>>). Delegates to the ONE
214
+ * grid-accumulation authority on the wall ATOM (`Wall.gridAdd`) — a DetectorWall
215
+ * is a Wall wrapper, so it consumes the atom's helper rather than keeping a
216
+ * second copy that could drift.
217
+ */
218
+ function _gridAdd(grid, y, z) {
219
+ Wall.gridAdd(grid, y, z);
220
+ }
221
+
222
+ /**
223
+ * Derive the Y-marginal (Map<y,count>) from a Y×Z grid (Map<y, Map<z,count>>) —
224
+ * sums every z-bucket for each y. Pure/static; exists so a caller can PROVE the
225
+ * grid and the legacy marginal agree (ratified §4.2 "Y-marginal DERIVED").
226
+ * Thin re-export of the atom's `Wall.deriveMarginalY` (one authority; the
227
+ * DetectorWall name stays as the published API its callers already use).
228
+ * @param {Map<number, Map<number,number>>} grid
229
+ * @returns {Map<number,number>}
230
+ */
231
+ DetectorWall.deriveMarginalY = function (grid) {
232
+ return Wall.deriveMarginalY(grid);
233
+ };
234
+
235
+ /**
236
+ * Get the X position of the detector wall.
237
+ */
238
+ Object.defineProperty(DetectorWall.prototype, 'x', {
239
+ get: function () { return this.wallX; },
240
+ set: function (val) { this.wallX = val; }
241
+ });
242
+
243
+ /**
244
+ * Accumulate this tick's collided frames into the running histogram and return it.
245
+ *
246
+ * G1 implementation: reads this LO's collided frames from the marshalled tick output
247
+ * (`tickData.objects[handle].frames`) — the engine snapshots them AFTER each op-program
248
+ * pass and BEFORE Step-7 cleanup, then marshals them out in Phase D (no per-LO query).
249
+ * Because the engine copies FrameData after the MARK/DETECT op-program has run, the
250
+ * frame already carries the slit provenance tag (e.g. 'slit1' or 'slit2') written by
251
+ * SlitScreen's slit LOs — read back here with `Tagger.readTag(fd)`.
252
+ *
253
+ * Bins by the frame's Y position (floor of `rect.y` / `fd.y`), matching the
254
+ * physical pixel row at which the frame hit the detector.
255
+ *
256
+ * The returned object holds RUNNING totals across all calls — each call adds
257
+ * the current tick's hits to the accumulators.
258
+ *
259
+ * Throw-guard: THROWS if called twice in a row
260
+ * with the SAME `tickData` object reference — each tick's data must be consumed
261
+ * exactly once, or collisions double-count silently. A correct caller (the CLI,
262
+ * FrqtlApp's onTick, the bench) calls this once per `universe.doTick()`
263
+ * result, so a fresh object reference every call means this never fires for them.
264
+ * Applies identically in 2D and 3D.
265
+ *
266
+ * @returns {{
267
+ * combined: Map<number,number>,
268
+ * slit1: Map<number,number>,
269
+ * slit2: Map<number,number>,
270
+ * total: number,
271
+ * slit1Total: number,
272
+ * slit2Total: number,
273
+ * grid: Map<number, Map<number,number>>,
274
+ * gridSlit1: Map<number, Map<number,number>>,
275
+ * gridSlit2: Map<number, Map<number,number>>
276
+ * }}
277
+ */
278
+ DetectorWall.prototype.getCollisionHistogram = function (tickData) {
279
+ // Throw-guard — top of function, before any read (THROW,
280
+ // never silent dedup). Reference equality only; two DIFFERENT tickData objects
281
+ // (even with identical content, e.g. the TICKS=0 `{objects:{}}` literal) never
282
+ // trip it — only the exact same object consumed twice in a row does.
283
+ if (tickData != null && tickData === this._lastTickData) {
284
+ throw new Error(
285
+ '[DetectorWall] getCollisionHistogram() called twice with the SAME TickData ' +
286
+ 'object — each tick must be consumed exactly once (fraqtl#61 double-count guard). ' +
287
+ 'Call universe.doTick() again before re-consuming.'
288
+ );
289
+ }
290
+ this._lastTickData = tickData;
291
+
292
+ var handle = this.detector_wall.handle;
293
+
294
+ // Read THIS LO's collected frames from the MARSHALLED tick output — the engine emits them as
295
+ // TickData.objects[handle].frames (it answers no per-LO collision QUERY; the universe stays
296
+ // focused on generic physics). IDENTICAL on both engines — no capability gap, no FAIL-LOUD
297
+ // guard. Each frame carries its provenance TAG, written in-tick by the SlitScreen slit LOs'
298
+ // compiled MARK op-program and read below through the Tagger part. This is a pure reader.
299
+ var frames = (tickData && tickData.objects && tickData.objects[handle] &&
300
+ Array.isArray(tickData.objects[handle].frames))
301
+ ? tickData.objects[handle].frames : [];
302
+
303
+ for (var i = 0; i < frames.length; i++) {
304
+ var fd = frames[i];
305
+
306
+ // Y bin: the frame's top-left Y. fd.rect.top (canonical wrap) or fd.y (flat snapshot).
307
+ var y;
308
+ if (fd.rect && fd.rect.top != null) {
309
+ y = Math.floor(fd.rect.top);
310
+ } else if (fd.y != null) {
311
+ y = Math.floor(fd.y);
312
+ } else {
313
+ y = 0;
314
+ }
315
+
316
+ // Z bin: stays 0 in every 2D run (fd.z defaults to 0 when
317
+ // the engine never wrote it), so the grid degenerates to one z-bucket per y.
318
+ var z = (fd.z != null) ? Math.floor(fd.z) : 0;
319
+
320
+ // Slit provenance — the TAGGER READ (the other half of the upstream slit LO's
321
+ // MARK write, which is the same part). This file names no bubble path and no
322
+ // key string: `Tagger.readTag(fd)` defaults to the canonical provenance key.
323
+ // Untagged frames read back as null (they go only into `combined`).
324
+ var slitName = Tagger.readTag(fd) || null;
325
+
326
+ // Y-marginal accumulation — UNTOUCHED from pre-B1 (protects the golden).
327
+ this._combined.set(y, (this._combined.get(y) || 0) + 1);
328
+ this._total++;
329
+
330
+ if (slitName === 'slit2') {
331
+ this._slit2.set(y, (this._slit2.get(y) || 0) + 1);
332
+ this._slit2Total++;
333
+ } else if (slitName === 'slit1') {
334
+ this._slit1.set(y, (this._slit1.get(y) || 0) + 1);
335
+ this._slit1Total++;
336
+ }
337
+ // Hits with no slit mark (slitName === null) go only into combined.
338
+
339
+ // Y×Z grid accumulation — additive, same frame data, same iteration.
340
+ _gridAdd(this._grid, y, z);
341
+ if (slitName === 'slit2') {
342
+ _gridAdd(this._gridSlit2, y, z);
343
+ } else if (slitName === 'slit1') {
344
+ _gridAdd(this._gridSlit1, y, z);
345
+ }
346
+ }
347
+
348
+ return {
349
+ combined: this._combined,
350
+ slit1: this._slit1,
351
+ slit2: this._slit2,
352
+ total: this._total,
353
+ slit1Total: this._slit1Total,
354
+ slit2Total: this._slit2Total,
355
+ grid: this._grid,
356
+ gridSlit1: this._gridSlit1,
357
+ gridSlit2: this._gridSlit2
358
+ };
359
+ };
360
+
361
+ /**
362
+ * Full teardown of what this detector composed: its inner Wall (and so its LO). Idempotent.
363
+ */
364
+ DetectorWall.prototype.dispose = function () {
365
+ this._innerWall.dispose();
366
+ };
367
+
368
+ /**
369
+ * Reset the accumulated histogram (e.g. when experiment parameters change).
370
+ */
371
+ DetectorWall.prototype.resetHistogram = function () {
372
+ this._combined = new Map();
373
+ this._slit1 = new Map();
374
+ this._slit2 = new Map();
375
+ this._total = 0;
376
+ this._slit1Total = 0;
377
+ this._slit2Total = 0;
378
+ // Y×Z grid — reset alongside the Y-marginal it derives from.
379
+ this._grid = new Map();
380
+ this._gridSlit1 = new Map();
381
+ this._gridSlit2 = new Map();
382
+ // NOTE: _lastTickData (the #61 throw-guard) is deliberately NOT reset here —
383
+ // resetting histogram DATA is orthogonal to the double-consume guard, which
384
+ // exists to catch a caller bug regardless of accumulator state.
385
+ };
386
+
387
+ // ── The kind (objects/kinds.js): descriptor, construction defaults, builder ──────────
388
+
389
+ /**
390
+ * DetectorWall — Absorbing detector screen (wraps a single LargeObject).
391
+ * Constructor: DetectorWall(universe, canvasRect, wallX, wallY,
392
+ * wallCollisionType, wallColor, wallHeight,
393
+ * wallWidth, wallName)
394
+ * The inner LargeObject always has actions=[COUNT, ABSORB].
395
+ * Group wrapper: the inner LargeObject is stamped with the ONE up-link `_owner` = <this>.
396
+ */
397
+ var DetectorWallDescriptor = {
398
+ description: 'A detector/absorber screen that counts and removes particles on contact. ' +
399
+ 'Used as the observation screen in quantum experiments (e.g. double-slit). ' +
400
+ 'Wraps a single LargeObject with COUNT+ABSORB actions. Accumulates a collision ' +
401
+ 'histogram (y-position → count) for interference pattern analysis.',
402
+ fields: [
403
+ {
404
+ name: 'wallX',
405
+ type: 'number',
406
+ constraint: { integer: true },
407
+ engineVisible: true,
408
+ description: 'Left edge X position (physics pixels)'
409
+ },
410
+ {
411
+ name: 'wallY',
412
+ type: 'number',
413
+ constraint: { integer: true },
414
+ engineVisible: true,
415
+ description: 'Top edge Y position (physics pixels)'
416
+ },
417
+ {
418
+ name: 'wallCollisionType',
419
+ type: 'enum',
420
+ constraint: { values: CollisionTypes },
421
+ engineVisible: true,
422
+ description: 'Collision type for the detector wall. Default NONE — absorption is via the ' +
423
+ 'ABSORB action in its actions list, not the collision type.'
424
+ },
425
+ {
426
+ name: 'wallColor',
427
+ type: 'rgb',
428
+ engineVisible: false,
429
+ description: 'Render color [r, g, b] of the detector wall'
430
+ },
431
+ {
432
+ name: 'wallHeight',
433
+ type: 'number',
434
+ constraint: { min: 1, integer: true },
435
+ engineVisible: true,
436
+ unit: 'px',
437
+ description: 'Height of the detector wall (physics pixels). Typically the full canvas height.'
438
+ },
439
+ {
440
+ name: 'wallWidth',
441
+ type: 'number',
442
+ constraint: { min: 1, integer: true },
443
+ engineVisible: true,
444
+ unit: 'px',
445
+ description: 'Width/thickness of the detector wall (physics pixels). Default 20.'
446
+ },
447
+ {
448
+ name: 'wallName',
449
+ type: 'string',
450
+ engineVisible: false,
451
+ description: 'Identifier string for this detector (display/debug only; not engine-visible after construction).'
452
+ }
453
+ ]
454
+ };
455
+
456
+ registerKind(DetectorWall, {
457
+ from: import.meta.url,
458
+ descriptor: DetectorWallDescriptor,
459
+ defaults: {
460
+ wallX: 180,
461
+ wallY: 0,
462
+ wallCollisionType: 'NONE',
463
+ wallColor: [120, 120, 120],
464
+ wallHeight: 200,
465
+ wallWidth: 20
466
+ },
467
+ build: function (props, ctx, Class) {
468
+ var DW = Class; // the class that registered (a composed fork's own class)
469
+ var world = ObjectSchema.worldRect(ctx.runner.canvas_rect, 'ObjectFactory create(\"DetectorWall\") runner.canvas_rect');
470
+ var pw = world.w;
471
+ var ph = world.h;
472
+ var dw = new DW(
473
+ ctx.runner.universe,
474
+ { w: pw, h: ph },
475
+ props.wallX != null ? props.wallX : Math.round(ctx.x),
476
+ props.wallY != null ? props.wallY : 0,
477
+ props.wallCollisionType || 'NONE',
478
+ props.wallColor || [120, 200, 120],
479
+ props.wallHeight || ph,
480
+ props.wallWidth || 20,
481
+ props.wallName || ('detector ' + Date.now())
482
+ );
483
+ if (!dw.detector_wall) {
484
+ throw new Error('[ObjectFactory] create("DetectorWall"): the DetectorWall was constructed but ' +
485
+ 'exposes no `detector_wall` (the registered inner LargeObject this kind hands back) — ' +
486
+ 'nothing placeable was built. (FAIL-LOUD.)');
487
+ }
488
+ dw._schemaKind = Class.name;
489
+ return dw;
490
+ }
491
+ });
492
+
493
+ // ── Export ───────────────────────────────────────────────────────────
494
+
495
+ export { DetectorWall };
496
+