@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,406 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * LargeObject — Engine-agnostic large object (wall, boundary, detector).
5
+ * Engine-agnostic: works with both the JS reference engine and the Rust/WASM engine.
6
+ *
7
+ * Uses handle-based API: calls universe.registerLargeObject(spec) and stores
8
+ * the opaque handle for subsequent mutations.
9
+ *
10
+ * Emits the CANONICAL spec in JS-native vocabulary:
11
+ * - collision_type: SCREAMING_SNAKE string
12
+ * - actions: SCREAMING_SNAKE array
13
+ * - reposition: literal string including 'closest'
14
+ * All engine-specific wire-format translation lives in the shim providers
15
+ * (compat/providers/js-engine.js and compat/providers/wasm-engine.js).
16
+ * No engine-sniffing above the shim (WS-A §2 invariant).
17
+ */
18
+
19
+ import * as ct from './CollisionTypes.js';
20
+ import * as ca from './CollisionActions.js';
21
+ import { compileActions as _compileActions } from '../objects/objects/actionCompiler.js';
22
+ import { Rectangle as _Rectangle } from './Rectangle.js';
23
+ import { registerKind } from '../objects/kinds.js';
24
+ import { SCOPE_DESCRIPTORS } from '../objects/schema/scopes.js';
25
+
26
+ var CollisionTypes, CollisionActions;
27
+
28
+ // PR-1 (Rectangle everywhere): this.rect is a REAL Rectangle instance (parts/Rectangle.js,
29
+ // imported — center↔left/top auto-recompute), not a hand-rolled {left,top,w,h} bag.
30
+
31
+ CollisionTypes = ct.CollisionTypes;
32
+ CollisionActions = ca.CollisionActions;
33
+
34
+ /**
35
+ * Build a LargeObject `.rect` for the 2D (4-element) case: a REAL Rectangle instance
36
+ * (is_centered=false) so a center write (`.x`/`.y`) auto-recomputes left/top and vice
37
+ * versa (PR-1 — audit item 3). A non-enumerable `toJSON` is attached so JSON-serializing
38
+ * consumers (codegen `_jsonCopy`, bundle export, save-state) still produce the SAME plain
39
+ * `{left,top,w,h}` shape they always did — the Rectangle's own enumerable fields are
40
+ * `_left/_x/...`, which WOULD drift; `toJSON` pins the wire shape. (`left/top/w/h` are read
41
+ * via getters by every direct consumer, so those are unaffected.) 6-element z/d rects are
42
+ * handled by the caller (a 2D Rectangle cannot carry z/d — see the constructor).
43
+ */
44
+ function _makeLoRect(left, top, w, h) {
45
+ if (typeof _Rectangle !== 'function') {
46
+ // Fail-soft: no Rectangle class in scope (degenerate env) → the legacy bare bag.
47
+ return { left: left, top: top, w: w, h: h };
48
+ }
49
+ var r = new _Rectangle(left, top, w, h, null, false); // is_centered=false → (left,top) corner
50
+ Object.defineProperty(r, 'toJSON', {
51
+ value: function () { return { left: this.left, top: this.top, w: this.w, h: this.h }; },
52
+ enumerable: false
53
+ });
54
+ return r;
55
+ }
56
+
57
+
58
+ // ── Helpers ─────────────────────────────────────────────────────────
59
+
60
+ /**
61
+ * Normalize a rect to array form: legacy [left, top, w, h] (4 elements), or the
62
+ * explicit-z/depth [left, top, z, w, h, d] (6 elements — FRQTL-MAINT 2026-07-01,
63
+ * MIRRORS the engine's Rectangle::from_top_left(l,t,z,w,h,d) argument order; see
64
+ * egpt_wasm/src/lib.rs rect_to_z_capable_rectangle). Accepts: [l,t,w,h],
65
+ * [l,t,z,w,h,d], {left,top,w,h}, {left,top,w,h,z,d}, or Rectangle instances.
66
+ * Pure shape coercion — no engine knowledge.
67
+ */
68
+ function rectToArray(rect) {
69
+ if (!rect) return null;
70
+ if (Array.isArray(rect)) return rect;
71
+ if (rect.left !== undefined) {
72
+ if (rect.z !== undefined || rect.d !== undefined) {
73
+ return [
74
+ rect.left, rect.top,
75
+ rect.z !== undefined ? rect.z : 0,
76
+ rect.w, rect.h,
77
+ rect.d !== undefined ? rect.d : rect.w,
78
+ ];
79
+ }
80
+ return [rect.left, rect.top, rect.w, rect.h];
81
+ }
82
+ if (rect.x !== undefined && rect.width !== undefined) return [rect.x, rect.y, rect.width, rect.height];
83
+ return rect;
84
+ }
85
+
86
+ // ── LargeObject ─────────────────────────────────────────────────────
87
+
88
+ /**
89
+ * Engine-agnostic large object wrapper.
90
+ *
91
+ * @param {Object} universe - The engine universe instance (JS or WASM)
92
+ * @param {Array|Object} rect - Bounding rectangle [left, top, w, h], {left,top,w,h},
93
+ * or — FRQTL-MAINT (2026-07-01), explicit z/depth — [left, top, z, w, h, d] /
94
+ * {left, top, w, h, z, d}. MIRRORS the engine's Rectangle::from_top_left(l,t,z,w,h,d)
95
+ * argument order (egpt_wasm/src/lib.rs rect_to_z_capable_rectangle). Omitting z/d
96
+ * is back-compat: the engine defaults z=0, d=w (bit-identical to the pre-fix
97
+ * hardcoded behavior) — WASM-only (the frozen JS reference engine is 2D-only and
98
+ * ignores any elements past index 3).
99
+ * IMPORTANT: always pass [left, top, w, h] (is_centered=false convention).
100
+ * The engine constructs Rectangle(left,top,w,h,null,false) internally.
101
+ * Never pass a center-based coordinate here — see Rectangle class (quadtree.js)
102
+ * where is_centered defaults to TRUE, a common footgun.
103
+ * @param {string} collision_type - CollisionTypes value (SCREAMING_SNAKE)
104
+ * @param {Array<string>} actions - CollisionActions values (SCREAMING_SNAKE)
105
+ * @param {string} name - Identifier
106
+ * @param {string} reposition - Eject direction: 'top','bottom','left','right','closest'
107
+ * @param {Array|null} observedDimensions - Dimension layers to observe (legacy param;
108
+ * prefer passing a single layer number via the `layer` property after construction,
109
+ * or pass [layer] here). null = object is visible to all dimensions.
110
+ * @param {number} search_padding - RESERVED / UNUSED positional placeholder. The engine no
111
+ * longer reads it: the collision search rect expands by the dimension's `mb_w`
112
+ * (EGPTfrqtl.js::update). Kept as positional arg #8 so `color` (#9) stays in place for the
113
+ * ~50 positional callers. Do not wire new behavior to it.
114
+ * @param {Array} color - RGB color [r, g, b]
115
+ * @param {boolean} [collectCollided=false] - G1: when true, the engine accumulates a
116
+ * point-in-time FrameData copy for each collided frame into a per-LO buffer
117
+ * (cleared at the start of each tick). Marshalled out in Phase D as
118
+ * `tickData.objects[handle].frames` (no per-LO collision query).
119
+ */
120
+ function LargeObject(universe, rect, collision_type, actions, name, reposition, observedDimensions, search_padding, color, collectCollided) {
121
+ // Defaults
122
+ actions = actions || [];
123
+ name = name || '';
124
+ reposition = reposition || 'closest';
125
+ observedDimensions = observedDimensions || null;
126
+ search_padding = (search_padding !== undefined) ? search_padding : 1;
127
+ color = color || [120, 120, 120];
128
+ collectCollided = collectCollided || false;
129
+
130
+ this.universe = universe;
131
+ this._schemaKind = 'LargeObject';
132
+ // rect is [left, top, w, h] (legacy) or [left, top, z, w, h, d] (FRQTL-MAINT
133
+ // 2026-07-01, explicit z/depth) — is_centered=false convention either way.
134
+ var ra = rectToArray(rect);
135
+ this.rect = (ra.length === 6)
136
+ // 3D z/d rect — the canonical Rectangle is 2D (x,y,left,top,right,bottom); it
137
+ // cannot carry z/d. Keep the explicit bare bag for WASM-only 3D LOs (Tube
138
+ // slabs — box-vs-plane derivation reads rect.d). PR-1 flag: a z-capable Rectangle is
139
+ // a separate, larger change; out of this pass's scope (see the build report).
140
+ ? { left: ra[0], top: ra[1], z: ra[2], w: ra[3], h: ra[4], d: ra[5] }
141
+ // 2D rect — a REAL Rectangle instance (PR-1): center↔left/top auto-recompute.
142
+ : _makeLoRect(ra[0], ra[1], ra[2], ra[3]);
143
+ this.color = color;
144
+ this.alpha = 255;
145
+ this.visible = true;
146
+ this.drawAsCircle = false;
147
+ this.name = name;
148
+ this.collision_type = collision_type;
149
+ this.reposition = reposition;
150
+ this.search_padding = search_padding;
151
+ // G1: mirror collect_collided flag on JS-side wrapper.
152
+ this.collect_collided = collectCollided;
153
+ // this.actions is set after compilation (see compilation block below)
154
+
155
+ // layer: the single dimension index this object is scoped to (null = all).
156
+ // Derived from observedDimensions: if observedDimensions is [n], layer = n.
157
+ // Settable directly as lo.layer = n; the schema field reads/writes this.
158
+ this.layer = (Array.isArray(observedDimensions) && observedDimensions.length === 1)
159
+ ? observedDimensions[0]
160
+ : null;
161
+
162
+ // ── Action → op-program compilation (the ONLY execution path) ───────
163
+ // Every action and the collision_type velocity response are lowered to an
164
+ // op-program here, BEFORE registerLargeObject(), so the engine receives the
165
+ // behaviour atomically with the registration. There is no native dispatch to
166
+ // fall back to, so there is nothing to catch: a compiler that will not load, or
167
+ // an action that will not compile, is a bug that must surface at construction —
168
+ // swallowing it registers a wall that silently does nothing.
169
+ var _compile = _compileActions;
170
+ var compiled = _compile(actions, name, reposition, collision_type);
171
+ var compiledProgram = compiled.program;
172
+ // A compiled program owns ALL behaviour, so the spec's collision_type goes to
173
+ // NONE — the engine must never be handed a second, competing description.
174
+ var nativeCollisionType = (compiledProgram !== null) ? CollisionTypes.NONE : collision_type;
175
+
176
+ // Mirror the DECLARED actions on the JS-side wrapper: they are what the inspector
177
+ // shows and what serializeDesign must round-trip to rebuild this object. (This
178
+ // used to mirror the compiler's leftovers, which were always empty — so a saved
179
+ // design lost its actions.)
180
+ this.actions = actions.slice();
181
+
182
+ // Mirror the compiled op-program so the schema inspector / conformance suite
183
+ // can read it from the JS-side wrapper. null if nothing compiled.
184
+ this.program = compiledProgram;
185
+
186
+ // Emit the WIRE spec — and ONLY what the engine actually has. A LargeObject is
187
+ // {rect, name, observed_dimensions, program, collect_collided, region}; there is no
188
+ // collision_type, actions or reposition field on the ABI, so sending them describes
189
+ // a configuration nothing listens to. Those three stay on the JS wrapper above,
190
+ // where the inspector and serializeDesign read them.
191
+ void nativeCollisionType;
192
+ var spec = {
193
+ rect: ra,
194
+ name: name,
195
+ observed_dimensions: observedDimensions || [],
196
+ };
197
+
198
+ // Attach the compiled op-program atomically (if any).
199
+ // Both JS and WASM engines accept program in the registerLargeObject spec.
200
+ if (compiledProgram !== null) {
201
+ spec.program = compiledProgram;
202
+ }
203
+
204
+ // G1: attach collect_collided to spec when requested.
205
+ // Only include when true to keep the wire-format clean (false is the default).
206
+ if (collectCollided) {
207
+ spec.collect_collided = true;
208
+ }
209
+
210
+ this.handle = universe.registerLargeObject(spec);
211
+
212
+ // ── SELF-REGISTER INTO THE WORLD'S WRAPPER CENSUS ────────────────────
213
+ // CONSTRUCTED = IN THE WORLD = ON SCREEN. This is the ONE wrapper construction
214
+ // seam, so this is where THE_MINIMAL_SIM's own sentence ("LARGE OBJECTS …
215
+ // self-registers") becomes true for RENDER as well as physics. Before this, a
216
+ // second `runner.addLargeObject(lo)` call was required to be seen at all — a
217
+ // shadow census whose divergence from the engine registry produced an object that
218
+ // collided and counted correctly and was invisible in every picture, silently.
219
+ //
220
+ // The census lives on the WORLD (not on a host runner) because the primitive
221
+ // surface must be reachable with no host at all: a headless rig constructs walls on
222
+ // a bare universe, and coupling the constructor to an ambient runner would break
223
+ // the split's first law. The renderer DERIVES its draw list from here.
224
+ //
225
+ // The array is created on demand so ANY universe object carries a census — there is
226
+ // one code path, not a "if the universe supports it" fork.
227
+ if (!Array.isArray(universe.largeObjects)) { universe.largeObjects = []; }
228
+ universe.largeObjects.push(this);
229
+ }
230
+
231
+ /**
232
+ * setRect(rect) — move/resize THIS LO: the JS wrapper's drawn rect AND the engine's
233
+ * registered rect, in ONE call (the same pair a Wall's setRect keeps in step). `rect` is the
234
+ * write wire in the LO's OWN form: `[l,t,w,h]` for a 2D LO, `[l,t,z,w,h,d]` for a z-capable
235
+ * one — a mismatched form THROWS (an LO's z-capability is fixed at construction). A part
236
+ * moving a member it owns (an emitter's Design marker) calls this. Zero physics: placement.
237
+ * @param {number[]} rect @returns {LargeObject} this
238
+ */
239
+ LargeObject.prototype.setRect = function (rect) {
240
+ var deep = (typeof this.rect.z === 'number');
241
+ if (!Array.isArray(rect) || rect.length !== (deep ? 6 : 4) ||
242
+ !rect.every(function (v) { return typeof v === 'number' && isFinite(v); })) {
243
+ throw new TypeError('[LargeObject.setRect] "' + this.name + '" is a ' + (deep ? 'z-capable' : '2D') +
244
+ ' LO and takes ' + (deep ? '[l,t,z,w,h,d]' : '[l,t,w,h]') + ' of finite numbers; got ' +
245
+ JSON.stringify(rect) + '. (FAIL-LOUD.)');
246
+ }
247
+ var r = this.rect;
248
+ if (deep) {
249
+ r.left = rect[0]; r.top = rect[1]; r.z = rect[2]; r.w = rect[3]; r.h = rect[4]; r.d = rect[5];
250
+ } else {
251
+ // w/h first, then the linked left/top setters (they read the new w/h).
252
+ r.w = rect[2]; r.h = rect[3]; r.left = rect[0]; r.top = rect[1];
253
+ }
254
+ if (this.universe && typeof this.universe.updateObjectProperty === 'function') {
255
+ this.universe.updateObjectProperty(this.handle, 'LargeObject', 'rect', rect.slice());
256
+ }
257
+ return this;
258
+ };
259
+
260
+ /**
261
+ * Change collision type at runtime (e.g. gate open/close).
262
+ * @param {string} ct - CollisionTypes value (SCREAMING_SNAKE)
263
+ */
264
+ LargeObject.prototype.setCollisionType = function (ct) {
265
+ this.collision_type = ct;
266
+ // Pass canonical SCREAMING_SNAKE; provider translates for target engine.
267
+ this.universe.setLargeObjectCollisionType(this.handle, ct);
268
+ };
269
+
270
+ /**
271
+ * Enable/disable this large object.
272
+ * @param {boolean} active
273
+ */
274
+ LargeObject.prototype.setActive = function (active) {
275
+ this.universe.setLargeObjectActive(this.handle, active);
276
+ };
277
+
278
+ /**
279
+ * Recompile this LargeObject's collision op-program from a NEW
280
+ * collision_type/actions pair and PUSH it to the running engine (WS Studio
281
+ * Controls Realignment S4, design §2.3.5 / CEO-ratified §4 call #7 —
282
+ * "flipping a wall into a detector mid-run is exactly the live-drive
283
+ * wonder"). `collision_type`/`actions` are compile-ONCE sugar inputs
284
+ * (actionCompiler.js lowers them to an op-program at REGISTRATION,
285
+ * `engineVisible:false` — see the schema field docs) — a bare
286
+ * `updateObjectProperty` mirror-write to either field updates ONLY this JS
287
+ * wrapper's display value, never the running op-program
288
+ * (`makeControlsForObject.js` scope note #5's "mirror-only" gap). This
289
+ * helper composes the SAME two primitives the constructor used at
290
+ * registration time — `actionCompiler.compileActions()` +
291
+ * `universe.updateObjectProperty(handle, 'LargeObject', 'program', …)` —
292
+ * so a live edit actually retargets the running behavior.
293
+ *
294
+ * `FrqtlApp.updateObjectProperty` calls this automatically when
295
+ * `prop` is `'collision_type'` or `'actions'` for a LargeObject/backdrop —
296
+ * callers do not need to call this directly under normal use.
297
+ *
298
+ * @param {Object} [opts]
299
+ * @param {string} [opts.collisionType] - new CollisionTypes value (SCREAMING_SNAKE).
300
+ * Defaults to this.collision_type (the current USER-FACING value — never
301
+ * the internal NONE the engine received when a program was compiled).
302
+ * @param {Array<string>} [opts.actions] - new CollisionActions[] (SCREAMING_SNAKE).
303
+ * Defaults to this.actions (the currently declared actions).
304
+ */
305
+ LargeObject.prototype.recompileActions = function (opts) {
306
+ opts = opts || {};
307
+ var newCollisionType = (opts.collisionType !== undefined) ? opts.collisionType : this.collision_type;
308
+ var newActions = (opts.actions !== undefined) ? opts.actions : this.actions;
309
+
310
+ var compile = _compileActions;
311
+ var compiled = compile(newActions, this.name, this.reposition, newCollisionType);
312
+
313
+ var nativeCollisionType = newCollisionType;
314
+ if (compiled.program !== null) {
315
+ // Same G2 rule as the constructor: a compiled program takes over ALL
316
+ // velocity behavior, so the engine's native collision_type must be NONE.
317
+ nativeCollisionType = CollisionTypes.NONE;
318
+ }
319
+
320
+ // Mirror the USER-FACING values on the wrapper (inspector/serialize source of
321
+ // truth) — collision_type stays the semantic value (e.g. 'WALL'), not the
322
+ // internal NONE sent below (matches the constructor's own this.collision_type
323
+ // assignment, which happens BEFORE compilation).
324
+ this.collision_type = newCollisionType;
325
+ this.actions = newActions.slice();
326
+ this.program = compiled.program;
327
+
328
+ if (this.universe && typeof this.universe.updateObjectProperty === 'function') {
329
+ this.universe.updateObjectProperty(this.handle, 'LargeObject', 'program', compiled.program);
330
+ }
331
+ };
332
+
333
+ /**
334
+ * Full teardown (WS Studio Controls Realignment S4, design item 1): unregister
335
+ * this LargeObject engine-side, removing its compiled collision op-program
336
+ * (actionCompiler.js's output) and its collision registration from the
337
+ * RUNNING engine — not just this JS wrapper's render-list membership.
338
+ * A render-list splice alone would leave the engine registration; without
339
+ * this call the engine keeps evaluating collisions against a "deleted"
340
+ * object forever. Idempotent — a second call (double-delete / teardown race)
341
+ * is a safe no-op.
342
+ *
343
+ * ONE call leaves BOTH censuses: `unregisterLargeObject` drops the engine
344
+ * registration AND splices this wrapper out of the world's wrapper census (which
345
+ * the renderer derives its draw list from), so a disposed object cannot keep
346
+ * drawing a body that no longer collides. A universe with no unregister seam (a
347
+ * bare stub) still loses its census entry here — the wrapper is gone either way.
348
+ */
349
+ LargeObject.prototype.dispose = function () {
350
+ if (this._disposed) { return; }
351
+ this._disposed = true;
352
+ if (this.universe && typeof this.universe.unregisterLargeObject === 'function') {
353
+ this.universe.unregisterLargeObject(this.handle);
354
+ } else if (this.universe && Array.isArray(this.universe.largeObjects)) {
355
+ var i = this.universe.largeObjects.indexOf(this);
356
+ if (i !== -1) { this.universe.largeObjects.splice(i, 1); }
357
+ }
358
+ };
359
+
360
+ // ── Statics ─────────────────────────────────────────────────────────
361
+ // rectToArray is a pure shape helper (no engine knowledge).
362
+ // Re-exported for consumers that need to normalize rects (e.g. CircuitGate).
363
+ LargeObject.rectToArray = rectToArray;
364
+
365
+ // ── The kind (objects/kinds.js): descriptor, construction defaults, builder ──────────
366
+
367
+
368
+ registerKind(LargeObject, {
369
+ from: import.meta.url,
370
+ descriptor: SCOPE_DESCRIPTORS.LargeObject, // the engine door's scope row (schema/scopes.js) — by reference, its ONE owner
371
+ defaults: {
372
+ collision_type: 'BOUNCE',
373
+ actions: [],
374
+ reposition: 'closest',
375
+ search_padding: 1,
376
+ color: [120, 120, 120]
377
+ },
378
+ build: function (props, ctx, Class) {
379
+ var LO = Class; // the class that registered (a composed fork's own class)
380
+ var bw = (props.rect && props.rect.w) || 70;
381
+ var bh = (props.rect && props.rect.h) || 70;
382
+ var bl = (props.rect && props.rect.left != null) ? props.rect.left : Math.round(ctx.x - bw / 2);
383
+ var bt = (props.rect && props.rect.top != null) ? props.rect.top : Math.round(ctx.y - bh / 2);
384
+ var ctype = props.collision_type || 'RANDOM_XY';
385
+ var actions = props.actions || [];
386
+ var loName = props.name || ('block ' + (ctx.runner.largeObjects.length + 1));
387
+ var reposition = props.reposition || 'closest';
388
+ var color = props.color || [210, 90, 90];
389
+ // layer → observedDimensions: pass [layer] to scope to a single dimension, null for all.
390
+ var loObsDims = (props.layer != null) ? [props.layer] : null;
391
+ // search_padding (arg #8) is a reserved/unused positional placeholder — the engine ignores
392
+ // it (collision search rect expands by dim.mb_w). Pass 1 to keep `color` (#9) in position.
393
+ var lo = new LO(ctx.runner.universe, [bl, bt, bw, bh], ctype, actions, loName,
394
+ reposition, loObsDims, 1, color);
395
+ if (props.alpha != null) { lo.alpha = props.alpha; }
396
+ if (props.visible != null) { lo.visible = props.visible; }
397
+ if (props.drawAsCircle != null) { lo.drawAsCircle = props.drawAsCircle; }
398
+ lo._schemaKind = Class.name;
399
+ return lo;
400
+ }
401
+ });
402
+
403
+ // ── Export ───────────────────────────────────────────────────────────
404
+
405
+ export { LargeObject };
406
+
@@ -0,0 +1,255 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * LightColor — canonical single-source color mapping for all renderers.
5
+ *
6
+ * This is the ONE color source for all renderers. Both p5 and three providers call
7
+ * LightColor.getRgbBySchemeName(scheme, v01) — no per-renderer color map,
8
+ * no per-shader color spectrum, no alpha ramp duplicated in any provider.
9
+ *
10
+ * Semantics: smooth chroma.js mix across the named-color stops — ONE chroma
11
+ * (assets/vendor/chroma.mjs) imported in every realm, so node and the browser
12
+ * compute the same colour.
13
+ *
14
+ * Export contract (ALL functions are pure; no side-effects):
15
+ * LightColor.getRgbColorFromFullness(v01) -> [r, g, b] (back-compat, calls warmcool)
16
+ * LightColor.getRgbColorFromFrequency(v01) -> [r, g, b] (new; 7-stop ROYGBIV, no pink)
17
+ * LightColor.getRgbBySchemeName(scheme, v01) -> [r, g, b] (dispatch; 'roygbiv'|'warmcool')
18
+ * LightColor.getAlphaFromIntensity(v01) -> number 0..1 (single alpha authority)
19
+ * LightColor.getColorNameFor(fullness) -> string
20
+ * LightColor.getFullnessForColorName(name) -> number
21
+ * LightColor.fullnessColorMap -> { name: threshold }
22
+ */
23
+
24
+ import chroma from '../assets/vendor/chroma.mjs';
25
+
26
+
27
+ // ── warmcool map (existing; back-compat) ─────────────────────────────────
28
+ // 9 stops red→…→violet→pink. Kept for existing experiments that use the
29
+ // default scheme. The pink terminus is an artifact of the original design;
30
+ // use 'roygbiv' for a spectrally correct ramp.
31
+ var fullnessColorMap = {
32
+ 'red': 0.1,
33
+ 'orange': 0.2,
34
+ 'yellow': 0.3,
35
+ 'green': 0.4,
36
+ 'cyan': 0.5,
37
+ 'blue': 0.6,
38
+ 'indigo': 0.7,
39
+ 'violet': 0.8,
40
+ 'pink': 0.9
41
+ };
42
+
43
+ // ── ROYGBIV map (new; spectrally clean, NO pink) ─────────────────────────
44
+ // 7 stops: red→orange→yellow→green→blue→indigo→violet.
45
+ // v01=0 = red (lowest frequency / highest fullness = most mass-dense), v01=1 = violet.
46
+ // Physics: fullness = mass/capacity = frequency proxy (wavelength = capacity/mass →
47
+ // frequency ∝ mass/capacity). Mapping fullness→ROYGBIV therefore maps frequency to
48
+ // spectrum with zero engine change.
49
+ var roygbivStops = [
50
+ { threshold: 0.0, name: 'red', rgb: [255, 0, 0] },
51
+ { threshold: 1/6, name: 'orange', rgb: [255, 127, 0] },
52
+ { threshold: 2/6, name: 'yellow', rgb: [255, 255, 0] },
53
+ { threshold: 3/6, name: 'green', rgb: [0, 200, 0] },
54
+ { threshold: 4/6, name: 'blue', rgb: [0, 0, 255] },
55
+ { threshold: 5/6, name: 'indigo', rgb: [75, 0, 130] },
56
+ { threshold: 1.0, name: 'violet', rgb: [148, 0, 211] }
57
+ ];
58
+
59
+
60
+
61
+
62
+ // chroma-based smooth mix for the warmcool ramp.
63
+ function _chromaWarmcool(fullness) {
64
+ var colorNames = Object.keys(fullnessColorMap);
65
+ var targetColor = colorNames[colorNames.length - 1];
66
+ for (var i = 0; i < colorNames.length; i++) {
67
+ if (fullness <= fullnessColorMap[colorNames[i]]) {
68
+ targetColor = colorNames[i];
69
+ break;
70
+ }
71
+ }
72
+ if (targetColor === colorNames[0]) {
73
+ var r0 = chroma(targetColor).rgb();
74
+ return [Math.round(r0[0]), Math.round(r0[1]), Math.round(r0[2])];
75
+ }
76
+ var targetIndex = colorNames.indexOf(targetColor);
77
+ var previousColor = colorNames[targetIndex - 1];
78
+ var mixRatio = (fullness - fullnessColorMap[previousColor]) /
79
+ (fullnessColorMap[targetColor] - fullnessColorMap[previousColor]);
80
+ var mixed = chroma.mix(previousColor, targetColor, isNaN(mixRatio) ? 0 : mixRatio);
81
+ var rgb = mixed.rgb();
82
+ return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])];
83
+ }
84
+
85
+ // chroma-based smooth mix for the ROYGBIV ramp.
86
+ function _chromaRoygbiv(v01) {
87
+ var v = Math.max(0, Math.min(1, v01));
88
+ var lo = roygbivStops[0], hi = roygbivStops[roygbivStops.length - 1];
89
+ for (var i = 0; i < roygbivStops.length - 1; i++) {
90
+ if (v <= roygbivStops[i + 1].threshold) {
91
+ lo = roygbivStops[i]; hi = roygbivStops[i + 1]; break;
92
+ }
93
+ }
94
+ var span = hi.threshold - lo.threshold;
95
+ var t = span > 0 ? (v - lo.threshold) / span : 0;
96
+ var mixed = chroma.mix(chroma.rgb(lo.rgb), chroma.rgb(hi.rgb), t);
97
+ var rgb = mixed.rgb();
98
+ return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])];
99
+ }
100
+
101
+ /**
102
+ * getAlphaFromIntensity — single alpha authority for ALL renderers.
103
+ *
104
+ * Layer 1 (API): v01 is the intensity proxy (fullness = mass/capacity ∈ [0,1]).
105
+ * Returns an alpha value in [0..1].
106
+ * Layer 2 (physics): fullness = frequency proxy (high fullness = high mass-density =
107
+ * high frequency). Dense quanta (high fullness) appear bright; sparse quanta
108
+ * (low fullness) appear dim — amplitude proportional to density.
109
+ *
110
+ * Dynamic range: `floor` (dim; near-empty quanta, default 0.35) to 1.0 (full alpha;
111
+ * full quanta). Previous per-provider floor was 180/255≈0.71 — too high, quanta could
112
+ * not read as dim. The wider default range (0.35..1.0) lets sparse quanta in the
113
+ * LOW×LOW lane of the collision matrix visually signal their sparsity.
114
+ *
115
+ * FRQTL-MAINT (2026-07-02, CEO render-tier-table ruling): `floor` is now an OPTIONAL
116
+ * second parameter (default 0.35 — every existing call site that omits it is
117
+ * byte-identical). This is the "extend, don't bypass" answer to the render-tier
118
+ * table's need for a true-invisible-below-a-mass-gate tier (e.g. air, Dim 2, mass≤2):
119
+ * that is NOT expressed by passing `floor:0` here (0 + v*0.65 is still nonzero for any
120
+ * v>0) — a genuine "invisible" tier is a SEPARATE, prior gate the CALLER applies
121
+ * (skip calling this function / force alpha=0 outright when the frame's mass is below
122
+ * the gate) BEFORE reaching for a dynamic-range floor at all. `floor` exists for tiers
123
+ * that want a DIFFERENT dim/bright dynamic range than the 0.35..1.0 default (e.g. a
124
+ * greyscale smoke tier that wants to read as dimmer overall) — it does not itself
125
+ * implement invisibility.
126
+ *
127
+ * @param {number} v01 - intensity value (fullness) in [0, 1]
128
+ * @param {number} [floor=0.35] - alpha at v01=0 (the low end of the dynamic range).
129
+ * @returns {number} alpha in [0, 1]
130
+ */
131
+ function getAlphaFromIntensity(v01, floor) {
132
+ var v = Math.max(0, Math.min(1, v01));
133
+ var f = (floor !== undefined && floor !== null) ? Math.max(0, Math.min(1, floor)) : 0.35;
134
+ // linear ramp: floor at v=0, 1.0 at v=1.
135
+ return f + v * (1 - f);
136
+ }
137
+
138
+ /**
139
+ * getRgbColorFromFrequency — 7-stop clean ROYGBIV ramp (NO pink terminus).
140
+ *
141
+ * Layer 1 (API): v01 is fullness (= frequency proxy). Returns [r,g,b] (0-255 ints).
142
+ * Layer 2 (physics): v01=0 → red (lowest frequency / sparsest), v01=1 → violet
143
+ * (highest frequency / densest). Spectrally correct light-spectrum ordering.
144
+ *
145
+ * @param {number} v01 - fullness / frequency proxy in [0, 1]
146
+ * @returns {number[]} [r, g, b] (0-255 ints)
147
+ */
148
+ function getRgbColorFromFrequency(v01) {
149
+ return _chromaRoygbiv(v01);
150
+ }
151
+
152
+ // ── greyscale ramp (FRQTL-MAINT 2026-07-02, CEO render-tier-table ruling) ────────
153
+ // §1 Dim→visual table: smoke/haze (the high-dim tiers) are "greyscale + alpha by
154
+ // density" — a neutral (no hue) ramp from dark (sparse) to near-white (dense), so
155
+ // density reads on BOTH channels (darker+more-transparent = sparse edges, brighter+
156
+ // more-opaque = dense core) — the standard smoke-rendering technique. 2-stop ramp
157
+ // (degenerate case of the same chroma mix every other scheme uses — no new
158
+ // interpolation code).
159
+ var greyscaleStops = [
160
+ { threshold: 0.0, name: 'greyscale-dark', rgb: [60, 60, 60] },
161
+ { threshold: 1.0, name: 'greyscale-light', rgb: [235, 235, 235] }
162
+ ];
163
+
164
+ function _chromaGreyscale(v01) {
165
+ var v = Math.max(0, Math.min(1, v01));
166
+ var mixed = chroma.mix(chroma.rgb(greyscaleStops[0].rgb), chroma.rgb(greyscaleStops[1].rgb), v);
167
+ var rgb = mixed.rgb();
168
+ return [Math.round(rgb[0]), Math.round(rgb[1]), Math.round(rgb[2])];
169
+ }
170
+
171
+ /**
172
+ * getRgbColorFromGreyscale — dark→light neutral ramp (density proxy, no hue).
173
+ *
174
+ * Layer 1 (API): v01 is fullness (density proxy). Returns [r,g,b] (0-255 ints).
175
+ * Layer 2 (physics): v01=0 → dark grey (sparse), v01=1 → near-white (dense) — the
176
+ * smoke/haze tiers (§1 Dim 4/5) read density via BOTH this ramp AND alpha
177
+ * (getAlphaFromIntensity), matching the standard smoke-rendering technique.
178
+ *
179
+ * @param {number} v01 - fullness / density proxy in [0, 1]
180
+ * @returns {number[]} [r, g, b] (0-255 ints)
181
+ */
182
+ function getRgbColorFromGreyscale(v01) {
183
+ return _chromaGreyscale(v01);
184
+ }
185
+
186
+ /**
187
+ * getRgbBySchemeName — single dispatch for named color schemes.
188
+ *
189
+ * Layer 1 (API): scheme ∈ 'roygbiv' | 'warmcool' | 'greyscale'. v01 is fullness in
190
+ * [0,1]. Unknown scheme → falls through to warmcool (back-compat). Returns [r,g,b]
191
+ * (0-255 ints).
192
+ * Layer 2 (physics): 'roygbiv' maps fullness→ROYGBIV spectrum (frequency-correct);
193
+ * 'warmcool' maps fullness→warm-to-cool legacy ramp (existing behavior); 'greyscale'
194
+ * (FRQTL-MAINT 2026-07-02) maps fullness→dark-to-light neutral ramp (the §1 smoke/
195
+ * haze tiers' shading mode).
196
+ *
197
+ * Both providers call THIS instead of calling getRgbColorFromFullness directly —
198
+ * no per-renderer color logic.
199
+ *
200
+ * @param {string} scheme - 'roygbiv' | 'warmcool' | 'greyscale'
201
+ * @param {number} v01 - fullness in [0, 1]
202
+ * @returns {number[]} [r, g, b] (0-255 ints)
203
+ */
204
+ function getRgbBySchemeName(scheme, v01) {
205
+ if (scheme === 'roygbiv') return getRgbColorFromFrequency(v01);
206
+ if (scheme === 'greyscale') return getRgbColorFromGreyscale(v01);
207
+ return getRgbColorFromFullness(v01); // 'warmcool' or unknown → back-compat
208
+ }
209
+
210
+ /**
211
+ * getRgbColorFromFullness — warmcool ramp (back-compat; callers prefer getRgbBySchemeName).
212
+ *
213
+ * chroma.js smooth mix (canonical semantics, every realm).
214
+ *
215
+ * @param {number} fullness - Value typically in 0..1 range
216
+ * @returns {number[]} [r, g, b] (0-255 ints)
217
+ */
218
+ function getRgbColorFromFullness(fullness) {
219
+ return _chromaWarmcool(fullness);
220
+ }
221
+
222
+ /**
223
+ * Get the named color for a fullness value (nearest ceiling threshold).
224
+ * @param {number} fullness
225
+ * @returns {string} Color name
226
+ */
227
+ function getColorNameFor(fullness) {
228
+ var colorNames = Object.keys(fullnessColorMap);
229
+ for (var i = 0; i < colorNames.length; i++) {
230
+ if (fullness <= fullnessColorMap[colorNames[i]]) return colorNames[i];
231
+ }
232
+ return 'pink';
233
+ }
234
+
235
+ /**
236
+ * Get the fullness threshold for a named color.
237
+ * @param {string} name
238
+ * @returns {number}
239
+ */
240
+ function getFullnessForColorName(name) {
241
+ return fullnessColorMap[name] !== undefined ? fullnessColorMap[name] : 0.5;
242
+ }
243
+
244
+ var LightColor = {
245
+ fullnessColorMap: fullnessColorMap,
246
+ getRgbColorFromFullness: getRgbColorFromFullness,
247
+ getRgbColorFromFrequency: getRgbColorFromFrequency,
248
+ getRgbColorFromGreyscale: getRgbColorFromGreyscale,
249
+ getRgbBySchemeName: getRgbBySchemeName,
250
+ getAlphaFromIntensity: getAlphaFromIntensity,
251
+ getColorNameFor: getColorNameFor,
252
+ getFullnessForColorName: getFullnessForColorName
253
+ };
254
+
255
+ export { LightColor };