@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,366 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * valueNormalizer.js — the DECLARED-TYPE write gate for object properties.
5
+ *
6
+ * The sibling of `schema/index.js` inside the ONE descriptor owner
7
+ * (`lib/frqtl/objects/schema/`): index.js declares WHAT type each field is, this file
8
+ * states what that declaration MEANS at a write. Its single public entry point is
9
+ * re-exported as `ObjectSchema.normalizeValue` — consumers call it through ObjectSchema
10
+ * and never import this file directly, so there is exactly one public surface.
11
+ *
12
+ * ── Layer 1: API contract ────────────────────────────────────────────────────
13
+ * normalizeValue(fieldDef, value, kind?) -> normalized value | THROW
14
+ * TYPES -> the row keys (asserted === FIELD_TYPES by
15
+ * schema/index.js at load time)
16
+ * RECT_TUPLE -> THE order of the rect write tuple, by length
17
+ * rectTupleText(n) -> that order as text, '[left, top, z, w, h, d]'
18
+ *
19
+ * ── Layer 2: physics behavior ────────────────────────────────────────────────
20
+ * None. A type gate on the write path — no engine state, no RNG draw. It REJECTS values
21
+ * the engine would otherwise have been handed as NaN geometry or a wrong wire type.
22
+ * Bench goldens cannot move (SU1).
23
+ *
24
+ * An ES module; objects/schema/index.js imports it.
25
+ */
26
+
27
+
28
+ // ── Declared-type VALUE normalization (the write-time gate) ───────────────
29
+ //
30
+ // ONE entry point, exported by the descriptor owner:
31
+ //
32
+ // ObjectSchema.normalizeValue(fieldDef, value, kind?) → normalized value | THROW
33
+ //
34
+ // A field descriptor DECLARES a type; this is where that declaration is ENFORCED at
35
+ // the write. Throw-or-apply: the call either returns the value in the declared type's
36
+ // canonical form, or throws naming kind/field/expected type. Never a silent accept —
37
+ // a wrong-typed value that is merely STORED poisons every later reader (a non-array
38
+ // `color` reaching the renderer throws `s.color.join is not a function` on every
39
+ // frame, far from the write that caused it), which is the same failure class as a
40
+ // silently no-op write.
41
+ //
42
+ // Consumers FERRY this; they never re-implement a per-type predicate:
43
+ // - lib/frqtl/objects/FrqtlApp.js `updateObjectProperty` — the write gate,
44
+ // calls it ONCE after the readonly check (this is also where the `boolean` 0/1
45
+ // coercion lives now; it used to be hand-inlined there as the first row of this
46
+ // table).
47
+ // - lib/frqtl/objects/objects/makeControlsForObject.js `_rgbHandler` — the READ-side
48
+ // "is this a valid rgb value" probe.
49
+ //
50
+ // Rows are keyed by the ONE field-type vocabulary (lib/shim/field-descriptor's
51
+ // FIELD_TYPES); `schema/index.js` asserts the row keys ARE that list at load time, so a
52
+ // 13th type added at that owner without a row here FAILS LOUD at boot rather than
53
+ // silently passing an unvalidated value through.
54
+ //
55
+ // Per-type contract:
56
+ // number — a finite JS number. A numeric STRING is NOT a number (the caller
57
+ // parses). `constraint.integer` ROUNDS (canonical form, the same
58
+ // normalization spirit as boolean 0/1 → false/true);
59
+ // `constraint.min`/`max` are ENFORCED (out of the declared range
60
+ // throws — a clamp would silently store something the caller never
61
+ // asked for).
62
+ // boolean — true | false | 0 | 1, stored as the boolean (generic controls carry
63
+ // booleans as a 0/1 scalar, and every consumer tests IDENTITY: a stored
64
+ // 0 reads as "still visible"). Anything else throws — `'false'` is truthy
65
+ // and would silently store `true`.
66
+ // string — a JS string (empty allowed).
67
+ // enum — a declared member: a KEY or a VALUE of `constraint.values` (that map
68
+ // is identity for every enum but SlitScreen.detectAtSlits, whose
69
+ // values are the numeric wire codes). A field declaring NO vocabulary
70
+ // validates the primitive shape only (string|number).
71
+ // enum-set — an ARRAY whose every element is a declared member (copy returned).
72
+ // rgb — STRICTLY an array of exactly 3 finite numbers (copy returned). CSV
73
+ // and hex are NOT accepted: the CSV 'r,g,b,a' form is the frame-data
74
+ // `string_map['render.color']` WIRE format owned by lib/shim/frame-data,
75
+ // and parsing it here would mint a second interpreter of that wire
76
+ // outside its owner. A caller holding CSV/hex converts at the format
77
+ // owner BEFORE the write.
78
+ // rect — the WRITE wire form: an array of 4 or 6 finite numbers in the order
79
+ // RECT_TUPLE names (below). Not the `{left,top,w,h}` READ shape —
80
+ // `updateObjectProperty` indexes the value positionally.
81
+ // rect-array — an array of rect-shaped arrays (deep copy returned).
82
+ // readonly — every write throws (derived/computed: inspectable, not editable).
83
+ // forbidden — every write throws (engine-owned topology).
84
+ // object — any non-null object, arrays included (op-program bags, `center`
85
+ // [x,y,z], a `region`). Returned by REFERENCE — these bags are live.
86
+ // writable — passthrough: a Frame-kind ACCESS-LEVEL tag, not a value type (the
87
+ // actual type is inferred from the live read).
88
+ //
89
+ // `null`/`undefined` pass through UNCHANGED for every type except readonly/forbidden:
90
+ // "unset" is a DECLARED, type-agnostic value across this schema — `layer: null` means
91
+ // all layers, a null LIVE override (SphereEmitter.renderShape / renderColor) means
92
+ // "scheme default". It is a legitimate write, not a type error.
93
+
94
+ function _describeValue(v) {
95
+ if (v === null) { return 'null'; }
96
+ if (v === undefined) { return 'undefined'; }
97
+ if (Array.isArray(v)) { return 'array(length ' + v.length + ')'; }
98
+ return typeof v;
99
+ }
100
+
101
+ function _previewValue(v) {
102
+ var s;
103
+ try { s = JSON.stringify(v); } catch (e) { s = String(v); }
104
+ if (s === undefined) { s = String(v); }
105
+ return (s.length > 60) ? (s.slice(0, 60) + '…') : s;
106
+ }
107
+
108
+ function _valueFail(kind, field, expected, value) {
109
+ throw new TypeError('[lib/frqtl/objects/schema] normalizeValue: ' +
110
+ (kind || '<kind>') + '.' + (field && field.name ? field.name : '<field>') +
111
+ ' is declared type "' + (field && field.type) + '" and expects ' + expected +
112
+ '; got ' + _describeValue(value) + ' ' + _previewValue(value) +
113
+ '. (FAIL-LOUD — throw-or-apply, never a silently accepted wrong type.)');
114
+ }
115
+
116
+ function _isFiniteNumber(v) { return typeof v === 'number' && isFinite(v); }
117
+
118
+ // ── THE rect write-tuple ORDER — one owner ────────────────────────────────
119
+ //
120
+ // The positional order of a `type:'rect'` write, keyed by tuple length. Every decode of a
121
+ // rect tuple (FrqtlApp.updateObjectProperty → Wall.setRect / LargeObject.setRect, the
122
+ // LargeObject constructor, LoDrawnCenter.rectAtCenter, worldRect below, and the engine's
123
+ // Rectangle::from_top_left(left, top, near, w, h, d) behind the WASM ABI) indexes in this
124
+ // order; `objects/test/rect-tuple-order.test.mjs` feeds each a tuple built BY NAME from this table and
125
+ // reads every field back by name. Every descriptor description that spells a tuple
126
+ // DERIVES its text from rectTupleText — never a hand-typed list.
127
+ var RECT_TUPLE = Object.freeze({
128
+ 4: Object.freeze(['left', 'top', 'w', 'h']),
129
+ 6: Object.freeze(['left', 'top', 'z', 'w', 'h', 'd'])
130
+ });
131
+
132
+ /** rectTupleText(n) → the order of the n-element rect tuple as text, e.g. '[left, top, z, w, h, d]'. */
133
+ function rectTupleText(n) {
134
+ if (!Object.prototype.hasOwnProperty.call(RECT_TUPLE, n)) {
135
+ throw new TypeError('[lib/frqtl/objects/schema] rectTupleText(' + _previewValue(n) +
136
+ '): a rect tuple has ' + Object.keys(RECT_TUPLE).join(' or ') + ' elements. (FAIL-LOUD.)');
137
+ }
138
+ return '[' + RECT_TUPLE[n].join(', ') + ']';
139
+ }
140
+
141
+ /**
142
+ * The declared members of an enum/enum-set field: the KEYS of constraint.values plus
143
+ * their VALUES (identical for every identity map; distinct for the numeric-wire-code
144
+ * map). Returns null when the field declares no vocabulary — the caller then validates
145
+ * the primitive shape only.
146
+ */
147
+ function _enumMembers(field) {
148
+ var values = (field.constraint && field.constraint.values) || null;
149
+ if (!values || typeof values !== 'object') { return null; }
150
+ var keys = Object.keys(values);
151
+ if (keys.length === 0) { return null; }
152
+ var members = keys.slice();
153
+ for (var i = 0; i < keys.length; i++) {
154
+ if (members.indexOf(values[keys[i]]) === -1) { members.push(values[keys[i]]); }
155
+ }
156
+ return members;
157
+ }
158
+
159
+ function _normalizeEnumMember(field, value, kind) {
160
+ var members = _enumMembers(field);
161
+ if (!members) {
162
+ if (typeof value !== 'string' && typeof value !== 'number') {
163
+ _valueFail(kind, field, 'a string or number (this field declares no ' +
164
+ 'constraint.values vocabulary to check against)', value);
165
+ }
166
+ return value;
167
+ }
168
+ if (members.indexOf(value) === -1) {
169
+ _valueFail(kind, field, 'one of [' + members.join(', ') + ']', value);
170
+ }
171
+ return value;
172
+ }
173
+
174
+ function _normalizeRectTuple(field, value, kind, what) {
175
+ if (!Array.isArray(value) || (value.length !== 4 && value.length !== 6) ||
176
+ !value.every(_isFiniteNumber)) {
177
+ _valueFail(kind, field, what, value);
178
+ }
179
+ return value.slice();
180
+ }
181
+
182
+ /**
183
+ * worldRect(r, where?) → { w, h[, left, top][, z, d] } — THE world-rect shape normalizer.
184
+ *
185
+ * ── Layer 1: API contract ────────────────────────────────────────────────
186
+ * THROW-OR-APPLY on a world/canvas rect handed to a part or factory. Accepts the
187
+ * canonical `{left, top, w, h}` bag (a `Rectangle` is one, structurally) or the rect
188
+ * tuple this file already owns (RECT_TUPLE).
189
+ * THROWS when `r` is absent or `w`/`h` is missing, non-numeric or non-positive — a
190
+ * zero-high detector counts nothing and would report it as physics. `{width, height}`
191
+ * is not a rect spelling and throws like any other missing dimension. The origin
192
+ * (`left`/`top`) and the z pair are FERRIED when present and never defaulted.
193
+ *
194
+ * The world rect has ONE writer — the co-init call (`EGPTUniverse.create({rect})` ←
195
+ * `new FrqtlApp({canvasW, canvasH})` / `exp.createCanvas(w, h, cfg)`); this only reads.
196
+ *
197
+ * ── Layer 2: physics behavior ────────────────────────────────────────────
198
+ * None. No engine state, no RNG draw.
199
+ *
200
+ * @param {Object|Array} r
201
+ * @param {string} [where] — the reading site, quoted in the error.
202
+ * @returns {{w:number, h:number, left?:number, top?:number, z?:number, d?:number}}
203
+ */
204
+ function worldRect(r, where) {
205
+ var site = where || 'worldRect(r)';
206
+ function fail(why) {
207
+ throw new TypeError('[lib/frqtl/objects/schema] ' + site + ': ' + why + '; got ' +
208
+ _describeValue(r) + ' ' + _previewValue(r) + '. A world rect is { left, top, w, h } ' +
209
+ '(or the ' + rectTupleText(4) + ' / ' + rectTupleText(6) + ' tuple) with positive w and h, ' +
210
+ 'written ONCE by the co-init call — EGPTUniverse.create({ rect }) ← new FrqtlApp({ ' +
211
+ 'canvasW, canvasH }) / exp.createCanvas(w, h, cfg). There is no default world size. ' +
212
+ '(FAIL-LOUD.)');
213
+ }
214
+ if (r === null || r === undefined || typeof r !== 'object') { fail('no world rect'); }
215
+ var out;
216
+ if (Array.isArray(r)) {
217
+ if ((r.length !== 4 && r.length !== 6) || !r.every(_isFiniteNumber)) {
218
+ fail('a rect tuple must be 4 or 6 finite numbers');
219
+ }
220
+ out = (r.length === 4)
221
+ ? { left: r[0], top: r[1], w: r[2], h: r[3] }
222
+ : { left: r[0], top: r[1], z: r[2], w: r[3], h: r[4], d: r[5] };
223
+ } else {
224
+ out = { w: r.w, h: r.h };
225
+ ['left', 'top', 'z', 'd'].forEach(function (k) {
226
+ if (r[k] === undefined || r[k] === null) { return; }
227
+ if (!_isFiniteNumber(r[k])) { fail('"' + k + '" must be a finite number'); }
228
+ out[k] = r[k];
229
+ });
230
+ }
231
+ if (!_isFiniteNumber(out.w) || !(out.w > 0)) { fail('w must be a positive finite number'); }
232
+ if (!_isFiniteNumber(out.h) || !(out.h > 0)) { fail('h must be a positive finite number'); }
233
+ return out;
234
+ }
235
+
236
+ var VALUE_NORMALIZERS = {
237
+ number: function (field, value, kind) {
238
+ if (!_isFiniteNumber(value)) {
239
+ _valueFail(kind, field, 'a finite number (a numeric STRING is not a ' +
240
+ 'number — parse it at the caller)', value);
241
+ }
242
+ var c = field.constraint || {};
243
+ var out = (c.integer === true) ? Math.round(value) : value;
244
+ if (typeof c.min === 'number' && out < c.min) {
245
+ _valueFail(kind, field, 'a value >= the declared min ' + c.min, value);
246
+ }
247
+ if (typeof c.max === 'number' && out > c.max) {
248
+ _valueFail(kind, field, 'a value <= the declared max ' + c.max, value);
249
+ }
250
+ return out;
251
+ },
252
+ boolean: function (field, value, kind) {
253
+ if (value === true || value === 1) { return true; }
254
+ if (value === false || value === 0) { return false; }
255
+ _valueFail(kind, field, 'true, false, 0 or 1', value);
256
+ },
257
+ string: function (field, value, kind) {
258
+ if (typeof value !== 'string') { _valueFail(kind, field, 'a string', value); }
259
+ return value;
260
+ },
261
+ 'enum': function (field, value, kind) {
262
+ return _normalizeEnumMember(field, value, kind);
263
+ },
264
+ 'enum-set': function (field, value, kind) {
265
+ if (!Array.isArray(value)) {
266
+ _valueFail(kind, field, 'an array of declared members', value);
267
+ }
268
+ for (var i = 0; i < value.length; i++) { _normalizeEnumMember(field, value[i], kind); }
269
+ return value.slice();
270
+ },
271
+ rgb: function (field, value, kind) {
272
+ if (!Array.isArray(value) || value.length !== 3 || !value.every(_isFiniteNumber)) {
273
+ _valueFail(kind, field, 'an array of exactly 3 finite numbers [r, g, b] ' +
274
+ '(a CSV "r,g,b,a" string is the frame-data render wire, not this ' +
275
+ 'field — convert at lib/shim/frame-data before the write)', value);
276
+ }
277
+ return value.slice();
278
+ },
279
+ rect: function (field, value, kind) {
280
+ return _normalizeRectTuple(field, value, kind,
281
+ 'an array of 4 finite numbers ' + rectTupleText(4) + ' or the 6-element ' +
282
+ 'explicit z/depth form ' + rectTupleText(6));
283
+ },
284
+ 'rect-array': function (field, value, kind) {
285
+ if (!Array.isArray(value)) {
286
+ _valueFail(kind, field, 'an array of rect tuples', value);
287
+ }
288
+ var out = [];
289
+ for (var i = 0; i < value.length; i++) {
290
+ out.push(_normalizeRectTuple(field, value[i], kind,
291
+ 'every entry to be an array of 4 finite numbers ' + rectTupleText(4) +
292
+ ' (or 6, ' + rectTupleText(6) + '); entry ' + i + ' is not'));
293
+ }
294
+ return out;
295
+ },
296
+ readonly: function (field, value, kind) {
297
+ _valueFail(kind, field, 'NO write at all — a readonly field is derived/' +
298
+ 'computed and inspectable only', value);
299
+ },
300
+ forbidden: function (field, value, kind) {
301
+ _valueFail(kind, field, 'NO write at all — this property is engine-owned ' +
302
+ 'topology', value);
303
+ },
304
+ object: function (field, value, kind) {
305
+ if (typeof value !== 'object') {
306
+ _valueFail(kind, field, 'a structured object (arrays included)', value);
307
+ }
308
+ return value; // by REFERENCE — op-program / region / center bags are live
309
+ },
310
+ writable: function (field, value) { return value; }
311
+ };
312
+
313
+ /**
314
+ * normalizeValue(fieldDef, value, kind) → the value in its declared canonical form.
315
+ *
316
+ * ── Layer 1: API contract ────────────────────────────────────────────────
317
+ * THROW-OR-APPLY. `fieldDef` is a descriptor field object (`ObjectSchema.get(kind)
318
+ * .fields[i]`); `kind` is optional context that only sharpens the error message.
319
+ * Throws a TypeError naming kind/field/declared-type/expected-form when the value
320
+ * cannot be accepted. See the per-type contract table above.
321
+ *
322
+ * ── Layer 2: physics behavior ────────────────────────────────────────────
323
+ * None. This is a type gate on the write path — it changes no engine state, draws no
324
+ * RNG, and rejects values the engine would otherwise have been handed as NaN
325
+ * geometry or a wrong wire type. Bench goldens cannot move (SU1).
326
+ *
327
+ * @param {{name:string, type:string, constraint?:object}} fieldDef
328
+ * @param {*} value
329
+ * @param {string} [kind] — the object kind, for the error message.
330
+ * @returns {*} the normalized value.
331
+ * @throws {TypeError} FAIL-LOUD on a wrong type / undeclared enum member / declared-
332
+ * constraint violation / any write to a readonly or forbidden field.
333
+ */
334
+ function normalizeValue(fieldDef, value, kind) {
335
+ if (!fieldDef || typeof fieldDef !== 'object' || typeof fieldDef.type !== 'string') {
336
+ throw new TypeError('[lib/frqtl/objects/schema] normalizeValue requires a field ' +
337
+ 'descriptor carrying a "type"; got ' + _describeValue(fieldDef) +
338
+ '. (FAIL-LOUD.)');
339
+ }
340
+ if (!Object.prototype.hasOwnProperty.call(VALUE_NORMALIZERS, fieldDef.type)) {
341
+ throw new TypeError('[lib/frqtl/objects/schema] normalizeValue: field "' +
342
+ fieldDef.name + '" declares type "' + fieldDef.type + '", which has no ' +
343
+ 'normalizer row. Field types are owned by lib/shim/field-descriptor ' +
344
+ '(FIELD_TYPES) — add the type THERE and a row in VALUE_NORMALIZERS here. ' +
345
+ '(FAIL-LOUD.)');
346
+ }
347
+ var row = VALUE_NORMALIZERS[fieldDef.type];
348
+ // readonly/forbidden reject EVERY write, the unset sentinel included.
349
+ if (fieldDef.type === 'readonly' || fieldDef.type === 'forbidden') {
350
+ return row(fieldDef, value, kind);
351
+ }
352
+ // The unset sentinel — a DECLARED, type-agnostic value here (layer:null = all
353
+ // layers; a null LIVE override = scheme default). A legitimate write, not a type
354
+ // error.
355
+ if (value === null || value === undefined) { return value; }
356
+ return row(fieldDef, value, kind);
357
+ }
358
+
359
+ // ── Export ────────────────────────────────────────────────────────────────────
360
+
361
+ // The row keys — schema/index.js asserts these are EXACTLY lib/shim/field-descriptor's FIELD_TYPES at
362
+ // load time (the type vocabulary has ONE owner; this is the drift gate, never a second vocabulary).
363
+ const TYPES = Object.freeze(Object.keys(VALUE_NORMALIZERS));
364
+
365
+ // RECT_TUPLE + rectTupleText: THE rect write-tuple order and its text form.
366
+ export { normalizeValue, worldRect, RECT_TUPLE, rectTupleText, TYPES };
@@ -0,0 +1,66 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * _collisionMatrixLanes — the ONE lane table the collision-matrix experiments sweep.
5
+ *
6
+ * The collision matrix is a DENSITY sweep. Density — `ρ = mass / capacity` — is the
7
+ * physical quantity the experiment reads: the per-tick exchange ceiling of
8
+ * `voxel_collision_pass` carries occupancy (ρ_A·ρ_B), so bounce TIMING emerges from
9
+ * density. Dense lanes complete the two-bucket exchange earliest; sparse lanes linger
10
+ * or pass through.
11
+ *
12
+ * The lanes are therefore declared as DENSITIES, and the absolute mass is derived from
13
+ * the capacity the ENGINE reports for the body it actually built. A hardcoded mass list
14
+ * is only correct for one capacity: `[1,8,64,512,1024,2048,4096]` spans ρ 0→1 for a 2D
15
+ * 64x64 voxel (capacity 4096) and collapses to ρ ≤ 0.0156 for the 3D 64x64x64 voxel
16
+ * (capacity 262144), which squeezes the whole sweep into its sparse corner and leaves
17
+ * the 3D scene with no dense lanes at all.
18
+ *
19
+ * The powers-of-two ladder is deliberate: collision probability goes as density², so a
20
+ * LOG scale is what reads cleanly across the transition (CEO 2026-06-29).
21
+ *
22
+ * @module _collisionMatrixLanes
23
+ */
24
+
25
+ /**
26
+ * Lane densities, sparse → full. `2^-12 … 2^0`.
27
+ *
28
+ * Against a 2D capacity of 4096 these reproduce the canonical mass ladder exactly:
29
+ * 1, 8, 64, 512, 1024, 2048, 4096.
30
+ *
31
+ * @type {Array<number>}
32
+ */
33
+ var LANE_DENSITIES = [
34
+ Math.pow(2, -12), Math.pow(2, -9), Math.pow(2, -6), Math.pow(2, -3),
35
+ Math.pow(2, -2), Math.pow(2, -1), 1
36
+ ];
37
+
38
+ /**
39
+ * Resolve the lane densities against a real capacity.
40
+ *
41
+ * @param {number} capacity - The body capacity the ENGINE reports (`mass` of a freshly
42
+ * built full body). 2^(2·level) in 2D, 2^(3·level) in 3D.
43
+ * @returns {Array<number>} one integer mass per lane, sparse → full, each ≥ 1 and
44
+ * clamped to `capacity`.
45
+ * @throws {Error} FAIL-LOUD when capacity is not a positive finite number — a lane
46
+ * ladder built against a bad capacity is the exact silent-wrong-scene this module
47
+ * exists to prevent.
48
+ */
49
+ function laneMassesForCapacity(capacity) {
50
+ if (!isFinite(capacity) || capacity <= 0) {
51
+ throw new Error('[collisionMatrixLanes] capacity must be a positive number, got ' +
52
+ JSON.stringify(capacity) + '. Read it back from the engine after building a ' +
53
+ 'body (universe.getFrameProperty(qid, \'mass\')) — never assume it.');
54
+ }
55
+ return LANE_DENSITIES.map(function (rho) {
56
+ return Math.max(1, Math.min(capacity, Math.round(rho * capacity)));
57
+ });
58
+ }
59
+
60
+ var api = {
61
+ LANE_DENSITIES: LANE_DENSITIES,
62
+ laneMassesForCapacity: laneMassesForCapacity
63
+ };
64
+
65
+ export { api as collisionMatrixLanes };
66
+
@@ -0,0 +1,118 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * _setupOptions — the ONE unknown-option-key validator for every `objects/setup/`
5
+ * factory.
6
+ *
7
+ * ## Why this exists
8
+ *
9
+ * Every setup factory reads its bag with the idiom
10
+ * `options.X !== undefined ? options.X : default` — 56 occurrences across 16 files,
11
+ * and NONE of them validated the key set. A misspelling therefore resolved to the
12
+ * DEFAULT and ran silently: a blackbody factory handed `{ temperatuer: 5000 }` ran a
13
+ * 300-quanta cavity, the badge stayed `ok`, and nothing anywhere said a word. This is the surface
14
+ * the notebooks' own markdown invites a reader to edit, so the failure mode is "you got
15
+ * a different experiment than the one you asked for, and you were never told."
16
+ *
17
+ * `EGPTUniverse.create()` and `EGPTUniverse.init()` already close exactly this hole one
18
+ * layer down (`wasm-engine.js` `_assertNoUnknownKeys`). This is the same rule at the
19
+ * factory layer, and it FAILS LOUD — no warn-path, no lenient mode, no opt-out flag.
20
+ *
21
+ * ## The contract, and who owns it
22
+ *
23
+ * A factory OWNS its option vocabulary, so the key list is declared in the factory's own
24
+ * file (as `setupX.OPTIONS`) beside the code that reads those keys — never re-enumerated
25
+ * here, and never copied from another module's schema. This module owns only the CHECK.
26
+ *
27
+ * Two consequences:
28
+ * 1. A factory that ferries its bag onward composes its list from the downstream
29
+ * owner's EXPORTED list rather than retyping it — e.g. `setupTunnelPointExit`
30
+ * hands an augmented bag to `setupSphereEmitter3D`, so its legal set is
31
+ * `OWN.concat(setupSphereEmitter3D.OPTIONS)`. Copying the downstream keys by hand
32
+ * would be the schema-mirror bug this workstream exists to remove.
33
+ * 2. `setup-factory-option-vocabulary.test.mjs` re-derives every factory's ACTUAL
34
+ * read-set by static trace and asserts the declared list covers it, so a key added
35
+ * to the code and forgotten in the list is a CI failure, not a caller's bad day.
36
+ *
37
+ * The message names every legal key, and — because the whole point is a typo — it leads
38
+ * with the nearest legal key when one is within edit distance 2.
39
+ */
40
+
41
+ import { ObjectSchema } from '../schema/index.js';
42
+
43
+ /** Levenshtein, capped: we only care about "is this within 2 edits". */
44
+ function _editDistance(a, b) {
45
+ if (Math.abs(a.length - b.length) > 2) return 99;
46
+ var prev = [], cur = [], i, j;
47
+ for (j = 0; j <= b.length; j++) prev[j] = j;
48
+ for (i = 1; i <= a.length; i++) {
49
+ cur[0] = i;
50
+ for (j = 1; j <= b.length; j++) {
51
+ cur[j] = Math.min(
52
+ prev[j] + 1,
53
+ cur[j - 1] + 1,
54
+ prev[j - 1] + (a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1)
55
+ );
56
+ }
57
+ prev = cur.slice();
58
+ }
59
+ return prev[b.length];
60
+ }
61
+
62
+ /** Nearest legal key within 2 edits (case-insensitive), or null. */
63
+ function _suggest(key, legalKeys) {
64
+ var best = null, bestD = 3, lk = key.toLowerCase();
65
+ for (var i = 0; i < legalKeys.length; i++) {
66
+ var d = _editDistance(lk, String(legalKeys[i]).toLowerCase());
67
+ if (d < bestD) { bestD = d; best = legalKeys[i]; }
68
+ }
69
+ return best;
70
+ }
71
+
72
+ /**
73
+ * Throw unless every own key of `options` is declared in `legalKeys`.
74
+ *
75
+ * @param {string} factoryName - e.g. 'setupDoubleSlit'. Named in the error.
76
+ * @param {Object} [options] - the caller's bag. null/undefined is legal (no options).
77
+ * @param {string[]} legalKeys - the factory's OWN exported vocabulary (`setupX.OPTIONS`).
78
+ * @returns {Object} the same `options` (or `{}`), so a factory can write
79
+ * `options = assertSetupOptions('setupX', options, OPTIONS);`
80
+ */
81
+ function assertSetupOptions(factoryName, options, legalKeys) {
82
+ if (options === undefined || options === null) return {};
83
+ if (typeof options !== 'object' || Array.isArray(options)) {
84
+ throw new Error(
85
+ '[' + factoryName + '] options must be a plain object, got ' +
86
+ (Array.isArray(options) ? 'an array' : typeof options) + '. ' +
87
+ 'Legal keys: ' + legalKeys.slice().sort().join(', ') + '.'
88
+ );
89
+ }
90
+ var sorted = legalKeys.slice().sort();
91
+ var keys = Object.keys(options);
92
+ for (var i = 0; i < keys.length; i++) {
93
+ if (legalKeys.indexOf(keys[i]) !== -1) continue;
94
+ var hint = _suggest(keys[i], legalKeys);
95
+ throw new Error(
96
+ '[' + factoryName + '] unknown option \'' + keys[i] + '\'' +
97
+ (hint ? ' — did you mean \'' + hint + '\'?' : '') +
98
+ ' This factory reads NONE of the keys it does not declare, so an ' +
99
+ 'unrecognized one would have silently resolved to a default and run a ' +
100
+ 'DIFFERENT experiment than you asked for. Legal options: ' +
101
+ sorted.join(', ') + '.'
102
+ );
103
+ }
104
+ return options;
105
+ }
106
+
107
+ /**
108
+ * requireWorldRect(where, rect) → `{ w, h, … }` — the world rect a factory builds in,
109
+ * through the ONE normalizer (`ObjectSchema.worldRect`). A factory never supplies a size:
110
+ * it hands `universe.universe_rect`, which the ONE construction authority set from the
111
+ * co-init call. Absent or malformed THROWS naming the co-init — never a default.
112
+ */
113
+ function requireWorldRect(where, rect) {
114
+ return ObjectSchema.worldRect(rect, where);
115
+ }
116
+
117
+ export { assertSetupOptions, requireWorldRect };
118
+