@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.
- package/CLAUDE.md +87 -0
- package/README.md +84 -2
- package/dist/dist-manifest.json +218 -0
- package/dist/eyes/index.js +109 -0
- package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
- package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
- package/dist/fat/pkg-node/package.json +12 -0
- package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
- package/dist/frqtl/compat/boot-node.js +34 -0
- package/dist/frqtl/compat/engine.js +127 -0
- package/dist/frqtl/compat/package-root.js +24 -0
- package/dist/frqtl/compat/providers/wasm-engine.js +2501 -0
- package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
- package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
- package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
- package/dist/frqtl/compat/renderer/Filters.js +163 -0
- package/dist/frqtl/compat/renderer/RenderFamily.js +85 -0
- package/dist/frqtl/compat/renderer/RenderMesh.js +100 -0
- package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
- package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
- package/dist/frqtl/compat/renderer/captureStill-common.js +259 -0
- package/dist/frqtl/compat/renderer/display-slot.js +96 -0
- package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
- package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
- package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
- package/dist/frqtl/compat/renderer/providers/index.js +22 -0
- package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
- package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
- package/dist/frqtl/compat/renderer/providers/three-provider.js +2474 -0
- package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
- package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
- package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
- package/dist/frqtl/compat/vendor.js +196 -0
- package/dist/frqtl/node-entry.js +17 -0
- package/dist/frqtl/objects/FrqtlApp.js +2922 -0
- package/dist/frqtl/objects/ObjectFactory.js +297 -0
- package/dist/frqtl/objects/RecipeContext.js +322 -0
- package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
- package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
- package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
- package/dist/frqtl/objects/instruments/capture-host.js +453 -0
- package/dist/frqtl/objects/instruments/capture.js +711 -0
- package/dist/frqtl/objects/instruments/hosted-eyes.js +985 -0
- package/dist/frqtl/objects/kinds-entry.js +14 -0
- package/dist/frqtl/objects/kinds.js +190 -0
- package/dist/frqtl/objects/objects/Box.js +273 -0
- package/dist/frqtl/objects/objects/Bullet.js +155 -0
- package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
- package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
- package/dist/frqtl/objects/objects/DirectedTunnel.js +412 -0
- package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
- package/dist/frqtl/objects/objects/LightSource.js +354 -0
- package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
- package/dist/frqtl/objects/objects/Tube.js +471 -0
- package/dist/frqtl/objects/objects/absorbRadiation.js +310 -0
- package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
- package/dist/frqtl/objects/objects/controlAction.js +89 -0
- package/dist/frqtl/objects/objects/controlBind.js +278 -0
- package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
- package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
- package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
- package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
- package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
- package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
- package/dist/frqtl/objects/objects/makeControlsForObject.js +824 -0
- package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
- package/dist/frqtl/objects/objects/slitLinerScatter.js +263 -0
- package/dist/frqtl/objects/objects/sustainedSource.js +299 -0
- package/dist/frqtl/objects/objects/tagger.js +228 -0
- package/dist/frqtl/objects/schema/engine-scopes.js +469 -0
- package/dist/frqtl/objects/schema/index.js +256 -0
- package/dist/frqtl/objects/schema/scopes.js +457 -0
- package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
- package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
- package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
- package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
- package/dist/frqtl/objects/setup/_worldInit.js +162 -0
- package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
- package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
- package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
- package/dist/frqtl/objects/setup/setupConeEmitter3D.js +251 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit.js +327 -0
- package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
- package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
- package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
- package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
- package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
- package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
- package/dist/frqtl/objects/setup/setupLightSourceLab.js +280 -0
- package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
- package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
- package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
- package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1259 -0
- package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
- package/dist/frqtl/objects/setup/setupTunnelLab.js +242 -0
- package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
- package/dist/frqtl/objects/setup/setupWaveInterference.js +178 -0
- package/dist/frqtl/objects/v1-replacements.js +30 -0
- package/dist/frqtl/package-core.js +23 -0
- package/dist/frqtl/parts/CameraControls.js +643 -0
- package/dist/frqtl/parts/CameraPath.js +575 -0
- package/dist/frqtl/parts/CollisionActions.js +35 -0
- package/dist/frqtl/parts/CollisionTypes.js +31 -0
- package/dist/frqtl/parts/DetectorWall.js +496 -0
- package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
- package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
- package/dist/frqtl/parts/FrqtlVision.js +818 -0
- package/dist/frqtl/parts/GraphingDetectorWall.js +490 -0
- package/dist/frqtl/parts/LargeObject.js +406 -0
- package/dist/frqtl/parts/LightColor.js +255 -0
- package/dist/frqtl/parts/LiveChart.js +285 -0
- package/dist/frqtl/parts/LogicGate.js +720 -0
- package/dist/frqtl/parts/OvenBox.js +612 -0
- package/dist/frqtl/parts/PointSource.js +763 -0
- package/dist/frqtl/parts/Rectangle.js +200 -0
- package/dist/frqtl/parts/SlitLightSource.js +234 -0
- package/dist/frqtl/parts/SlitScreen.js +628 -0
- package/dist/frqtl/parts/SphereEmitter.js +946 -0
- package/dist/frqtl/parts/Trace.js +508 -0
- package/dist/frqtl/parts/Tunnel.js +1057 -0
- package/dist/frqtl/parts/Wall.js +1211 -0
- package/dist/frqtl/parts/index.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
- package/dist/frqtl/scenes/absorbing-wall.js +32 -0
- package/dist/frqtl/scenes/atomic-model.js +88 -0
- package/dist/frqtl/scenes/big-bang.js +46 -0
- package/dist/frqtl/scenes/blackbody.js +81 -0
- package/dist/frqtl/scenes/circuit-sat.js +127 -0
- package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
- package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
- package/dist/frqtl/scenes/counting-wall.js +38 -0
- package/dist/frqtl/scenes/double-slit.js +31 -0
- package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
- package/dist/frqtl/scenes/dse-one-slit.js +114 -0
- package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
- package/dist/frqtl/scenes/dse-tunnel-rebuild.js +244 -0
- package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
- package/dist/frqtl/scenes/dse-two-slits.js +132 -0
- package/dist/frqtl/scenes/empty.js +14 -0
- package/dist/frqtl/scenes/laser-buildup.js +49 -0
- package/dist/frqtl/scenes/mirror-3d.js +56 -0
- package/dist/frqtl/scenes/mirror-route.js +140 -0
- package/dist/frqtl/scenes/mirror.js +38 -0
- package/dist/frqtl/scenes/omni-3d.js +27 -0
- package/dist/frqtl/scenes/omni-source.js +22 -0
- package/dist/frqtl/scenes/self-interference.js +39 -0
- package/dist/frqtl/scenes/smoking-gun.js +122 -0
- package/dist/frqtl/scenes/straight-walk.js +22 -0
- package/dist/frqtl/scenes/sun.js +39 -0
- package/dist/frqtl/scenes/two-source.js +37 -0
- package/dist/frqtl/scenes/wave.js +26 -0
- package/dist/shim/bundle/frqnb.js +112 -0
- package/dist/shim/bundle/index.js +121 -0
- package/dist/shim/bundle/indexHtml.js +107 -0
- package/dist/shim/bundle/manifest.js +95 -0
- package/dist/shim/bundle/zip.js +243 -0
- package/dist/shim/bus.js +67 -0
- package/dist/shim/controls/index.js +627 -0
- package/dist/shim/controls/registry.js +274 -0
- package/dist/shim/engine/artifact-shell.js +212 -0
- package/dist/shim/engine/boot-phases.js +44 -0
- package/dist/shim/engine/engine-stack-boot.js +59 -0
- package/dist/shim/engine/router.js +692 -0
- package/dist/shim/experiment-frame/index.js +296 -0
- package/dist/shim/expr/ast-schema.js +369 -0
- package/dist/shim/field-descriptor/index.js +326 -0
- package/dist/shim/frame-data/index.js +224 -0
- package/dist/shim/index.js +67 -0
- package/dist/shim/notebook/canvas-guard.js +82 -0
- package/dist/shim/notebook/catalog.js +35 -0
- package/dist/shim/notebook/format.js +736 -0
- package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
- package/dist/shim/notebook/kernel.js +672 -0
- package/dist/shim/notebook/knob.js +151 -0
- package/dist/shim/notebook/module-resolver.js +260 -0
- package/dist/shim/notebook/notebook-knobs.js +65 -0
- package/dist/shim/notebook/produced-bindings.js +67 -0
- package/dist/shim/notebook/realm-registry.js +121 -0
- package/dist/shim/notebook/realm-transport.js +73 -0
- package/dist/shim/notebook/setup-builder.js +510 -0
- package/dist/shim/notebook/store.js +159 -0
- package/dist/shim/notebook/vendor-importmap.js +134 -0
- package/dist/shim/notebook/view-builder.js +189 -0
- package/dist/shim/notebook/view-ctx.js +100 -0
- package/dist/shim/notebook/view-handle.js +202 -0
- package/dist/shim/notebook/warmup.js +102 -0
- package/dist/shim/package.json +4 -0
- package/dist/shim/page-params/index.js +214 -0
- package/dist/shim/project/identity.js +112 -0
- package/dist/shim/project/index.js +19 -0
- package/dist/shim/project/link.js +402 -0
- package/dist/shim/project/node-specifier-hook.mjs +35 -0
- package/dist/shim/project/register-node.mjs +17 -0
- package/dist/shim/project/specifiers.js +106 -0
- package/dist/shim/registry/copy-gate.js +110 -0
- package/dist/shim/registry/exhibits.js +455 -0
- package/dist/shim/registry/experiments.js +215 -0
- package/dist/shim/registry/index.js +19 -0
- package/dist/shim/registry/scene-source.js +157 -0
- package/dist/shim/registry/walkthrough.js +807 -0
- package/dist/shim/render/capture-envelope.js +279 -0
- package/dist/shim/render/heat-colormap.js +92 -0
- package/dist/shim/render/render-stages.js +855 -0
- package/dist/shim/render/sun-look.js +33 -0
- package/dist/shim/render-state/apply.js +262 -0
- package/dist/shim/render-state/index.js +74 -0
- package/dist/shim/rpc/index.js +207 -0
- package/dist/shim/runtime/index.js +1023 -0
- package/dist/shim/storage/boot.js +69 -0
- package/dist/shim/storage/index.js +169 -0
- package/dist/shim/storage/local-storage-provider.js +230 -0
- package/dist/shim/storage/make-namespace-store.js +132 -0
- package/dist/shim/storage/modules.js +249 -0
- package/dist/shim/storage/projects.js +150 -0
- package/llms.txt +12 -0
- 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 };
|