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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. package/CLAUDE.md +87 -0
  2. package/LICENSE.md +664 -0
  3. package/README.md +84 -2
  4. package/dist/dist-manifest.json +218 -0
  5. package/dist/eyes/index.js +109 -0
  6. package/dist/fat/pkg-node/egpt_wasm.js +1277 -0
  7. package/dist/fat/pkg-node/egpt_wasm_bg.wasm +0 -0
  8. package/dist/fat/pkg-node/package.json +12 -0
  9. package/dist/frqtl/assets/vendor/chroma.mjs +64 -0
  10. package/dist/frqtl/compat/boot-node.js +34 -0
  11. package/dist/frqtl/compat/engine.js +127 -0
  12. package/dist/frqtl/compat/package-root.js +24 -0
  13. package/dist/frqtl/compat/providers/wasm-engine.js +2499 -0
  14. package/dist/frqtl/compat/providers/wasm-load-browser.js +134 -0
  15. package/dist/frqtl/compat/providers/wasm-load-node.js +59 -0
  16. package/dist/frqtl/compat/renderer/FieldBuilder.js +145 -0
  17. package/dist/frqtl/compat/renderer/Filters.js +163 -0
  18. package/dist/frqtl/compat/renderer/RenderFamily.js +84 -0
  19. package/dist/frqtl/compat/renderer/RenderMesh.js +99 -0
  20. package/dist/frqtl/compat/renderer/RenderShape.js +154 -0
  21. package/dist/frqtl/compat/renderer/camera2d-geometry.js +183 -0
  22. package/dist/frqtl/compat/renderer/captureStill-common.js +258 -0
  23. package/dist/frqtl/compat/renderer/display-slot.js +96 -0
  24. package/dist/frqtl/compat/renderer/lo-drawn-center.js +231 -0
  25. package/dist/frqtl/compat/renderer/orbit-bag.js +216 -0
  26. package/dist/frqtl/compat/renderer/providers/godrays-layer.js +213 -0
  27. package/dist/frqtl/compat/renderer/providers/index.js +22 -0
  28. package/dist/frqtl/compat/renderer/providers/p5-provider.js +861 -0
  29. package/dist/frqtl/compat/renderer/providers/three-camera-contract.js +160 -0
  30. package/dist/frqtl/compat/renderer/providers/three-camera-geometry.js +571 -0
  31. package/dist/frqtl/compat/renderer/providers/three-provider.js +2470 -0
  32. package/dist/frqtl/compat/renderer/renderCapabilities-common.js +186 -0
  33. package/dist/frqtl/compat/renderer/renderer-provider-contract.js +238 -0
  34. package/dist/frqtl/compat/renderer/unprojectStill.js +172 -0
  35. package/dist/frqtl/compat/vendor.js +196 -0
  36. package/dist/frqtl/node-entry.js +17 -0
  37. package/dist/frqtl/objects/FrqtlApp.js +2920 -0
  38. package/dist/frqtl/objects/ObjectFactory.js +296 -0
  39. package/dist/frqtl/objects/RecipeContext.js +322 -0
  40. package/dist/frqtl/objects/collectors/DataCollector.js +116 -0
  41. package/dist/frqtl/objects/constants/WallBehaviors.js +178 -0
  42. package/dist/frqtl/objects/geometry/SlabFace.js +258 -0
  43. package/dist/frqtl/objects/instruments/capture-host.js +453 -0
  44. package/dist/frqtl/objects/instruments/capture.js +710 -0
  45. package/dist/frqtl/objects/instruments/hosted-eyes.js +984 -0
  46. package/dist/frqtl/objects/kinds-entry.js +14 -0
  47. package/dist/frqtl/objects/kinds.js +190 -0
  48. package/dist/frqtl/objects/objects/Box.js +273 -0
  49. package/dist/frqtl/objects/objects/Bullet.js +155 -0
  50. package/dist/frqtl/objects/objects/CircuitGate.js +380 -0
  51. package/dist/frqtl/objects/objects/CircuitProbe.js +214 -0
  52. package/dist/frqtl/objects/objects/DirectedTunnel.js +411 -0
  53. package/dist/frqtl/objects/objects/EgptFractal.js +164 -0
  54. package/dist/frqtl/objects/objects/LightSource.js +351 -0
  55. package/dist/frqtl/objects/objects/QuantumEmitter.js +760 -0
  56. package/dist/frqtl/objects/objects/Tube.js +471 -0
  57. package/dist/frqtl/objects/objects/absorbRadiation.js +309 -0
  58. package/dist/frqtl/objects/objects/actionCompiler.js +1747 -0
  59. package/dist/frqtl/objects/objects/controlAction.js +89 -0
  60. package/dist/frqtl/objects/objects/controlBind.js +278 -0
  61. package/dist/frqtl/objects/objects/directedRedirectProgram.js +120 -0
  62. package/dist/frqtl/objects/objects/emitterProgram.js +209 -0
  63. package/dist/frqtl/objects/objects/frqtl-control-slider.js +197 -0
  64. package/dist/frqtl/objects/objects/frqtl-control-sync.js +272 -0
  65. package/dist/frqtl/objects/objects/frqtl-control-widgets.js +219 -0
  66. package/dist/frqtl/objects/objects/frqtl-control-write-queue.js +174 -0
  67. package/dist/frqtl/objects/objects/makeControlsForObject.js +820 -0
  68. package/dist/frqtl/objects/objects/makeControlsForRenderSurface.js +161 -0
  69. package/dist/frqtl/objects/objects/slitLinerScatter.js +261 -0
  70. package/dist/frqtl/objects/objects/sustainedSource.js +298 -0
  71. package/dist/frqtl/objects/objects/tagger.js +228 -0
  72. package/dist/frqtl/objects/schema/engine-scopes.js +468 -0
  73. package/dist/frqtl/objects/schema/index.js +256 -0
  74. package/dist/frqtl/objects/schema/scopes.js +457 -0
  75. package/dist/frqtl/objects/schema/validate-descriptor.js +49 -0
  76. package/dist/frqtl/objects/schema/valueNormalizer.js +366 -0
  77. package/dist/frqtl/objects/setup/_collisionMatrixLanes.js +66 -0
  78. package/dist/frqtl/objects/setup/_setupOptions.js +118 -0
  79. package/dist/frqtl/objects/setup/_worldInit.js +162 -0
  80. package/dist/frqtl/objects/setup/setupBlackbodySubstrate.js +242 -0
  81. package/dist/frqtl/objects/setup/setupCircuitBasic.js +447 -0
  82. package/dist/frqtl/objects/setup/setupCollisionMatrix.js +188 -0
  83. package/dist/frqtl/objects/setup/setupConeEmitter3D.js +250 -0
  84. package/dist/frqtl/objects/setup/setupDoubleSlit.js +326 -0
  85. package/dist/frqtl/objects/setup/setupDoubleSlit_Laser.js +229 -0
  86. package/dist/frqtl/objects/setup/setupEmpty.js +59 -0
  87. package/dist/frqtl/objects/setup/setupFxSmoke.js +209 -0
  88. package/dist/frqtl/objects/setup/setupG1Race.js +80 -0
  89. package/dist/frqtl/objects/setup/setupG2Sweep.js +89 -0
  90. package/dist/frqtl/objects/setup/setupG3Collide.js +177 -0
  91. package/dist/frqtl/objects/setup/setupLightSourceLab.js +277 -0
  92. package/dist/frqtl/objects/setup/setupOscillator.js +68 -0
  93. package/dist/frqtl/objects/setup/setupParticleWalk.js +93 -0
  94. package/dist/frqtl/objects/setup/setupParticleWalk3D.js +133 -0
  95. package/dist/frqtl/objects/setup/setupSphereEmitter3D.js +1258 -0
  96. package/dist/frqtl/objects/setup/setupTraceCircuit.js +239 -0
  97. package/dist/frqtl/objects/setup/setupTunnelLab.js +240 -0
  98. package/dist/frqtl/objects/setup/setupTunnelPointExit.js +306 -0
  99. package/dist/frqtl/objects/setup/setupWaveInterference.js +177 -0
  100. package/dist/frqtl/objects/v1-replacements.js +29 -0
  101. package/dist/frqtl/package-core.js +23 -0
  102. package/dist/frqtl/parts/CameraControls.js +643 -0
  103. package/dist/frqtl/parts/CameraPath.js +575 -0
  104. package/dist/frqtl/parts/CollisionActions.js +34 -0
  105. package/dist/frqtl/parts/CollisionTypes.js +30 -0
  106. package/dist/frqtl/parts/DetectorWall.js +496 -0
  107. package/dist/frqtl/parts/FrqtlControlOverlay.js +227 -0
  108. package/dist/frqtl/parts/FrqtlControlsPanel.js +415 -0
  109. package/dist/frqtl/parts/FrqtlVision.js +818 -0
  110. package/dist/frqtl/parts/GraphingDetectorWall.js +489 -0
  111. package/dist/frqtl/parts/LargeObject.js +405 -0
  112. package/dist/frqtl/parts/LightColor.js +255 -0
  113. package/dist/frqtl/parts/LiveChart.js +285 -0
  114. package/dist/frqtl/parts/LogicGate.js +717 -0
  115. package/dist/frqtl/parts/OvenBox.js +611 -0
  116. package/dist/frqtl/parts/PointSource.js +762 -0
  117. package/dist/frqtl/parts/Rectangle.js +200 -0
  118. package/dist/frqtl/parts/SlitLightSource.js +234 -0
  119. package/dist/frqtl/parts/SlitScreen.js +628 -0
  120. package/dist/frqtl/parts/SphereEmitter.js +946 -0
  121. package/dist/frqtl/parts/Trace.js +508 -0
  122. package/dist/frqtl/parts/Tunnel.js +1057 -0
  123. package/dist/frqtl/parts/Wall.js +1211 -0
  124. package/dist/frqtl/parts/index.js +33 -0
  125. package/dist/frqtl/scenes/absorbing-wall-3d.js +33 -0
  126. package/dist/frqtl/scenes/absorbing-wall.js +32 -0
  127. package/dist/frqtl/scenes/atomic-model.js +88 -0
  128. package/dist/frqtl/scenes/big-bang.js +46 -0
  129. package/dist/frqtl/scenes/blackbody.js +81 -0
  130. package/dist/frqtl/scenes/circuit-sat.js +127 -0
  131. package/dist/frqtl/scenes/collision-matrix-3d.js +107 -0
  132. package/dist/frqtl/scenes/counting-wall-3d.js +36 -0
  133. package/dist/frqtl/scenes/counting-wall.js +38 -0
  134. package/dist/frqtl/scenes/double-slit.js +31 -0
  135. package/dist/frqtl/scenes/dse-one-emitter.js +196 -0
  136. package/dist/frqtl/scenes/dse-one-slit.js +114 -0
  137. package/dist/frqtl/scenes/dse-photoelectric.js +70 -0
  138. package/dist/frqtl/scenes/dse-tunnel-rebuild.js +243 -0
  139. package/dist/frqtl/scenes/dse-two-emitters.js +196 -0
  140. package/dist/frqtl/scenes/dse-two-slits.js +132 -0
  141. package/dist/frqtl/scenes/empty.js +14 -0
  142. package/dist/frqtl/scenes/laser-buildup.js +49 -0
  143. package/dist/frqtl/scenes/mirror-3d.js +56 -0
  144. package/dist/frqtl/scenes/mirror-route.js +140 -0
  145. package/dist/frqtl/scenes/mirror.js +38 -0
  146. package/dist/frqtl/scenes/omni-3d.js +27 -0
  147. package/dist/frqtl/scenes/omni-source.js +22 -0
  148. package/dist/frqtl/scenes/self-interference.js +39 -0
  149. package/dist/frqtl/scenes/smoking-gun.js +122 -0
  150. package/dist/frqtl/scenes/straight-walk.js +22 -0
  151. package/dist/frqtl/scenes/sun.js +39 -0
  152. package/dist/frqtl/scenes/two-source.js +37 -0
  153. package/dist/frqtl/scenes/wave.js +26 -0
  154. package/dist/shim/bundle/frqnb.js +112 -0
  155. package/dist/shim/bundle/index.js +121 -0
  156. package/dist/shim/bundle/indexHtml.js +107 -0
  157. package/dist/shim/bundle/manifest.js +95 -0
  158. package/dist/shim/bundle/zip.js +243 -0
  159. package/dist/shim/bus.js +67 -0
  160. package/dist/shim/controls/index.js +627 -0
  161. package/dist/shim/controls/registry.js +274 -0
  162. package/dist/shim/engine/artifact-shell.js +211 -0
  163. package/dist/shim/engine/boot-phases.js +44 -0
  164. package/dist/shim/engine/engine-stack-boot.js +59 -0
  165. package/dist/shim/engine/router.js +690 -0
  166. package/dist/shim/experiment-frame/index.js +296 -0
  167. package/dist/shim/expr/ast-schema.js +369 -0
  168. package/dist/shim/field-descriptor/index.js +325 -0
  169. package/dist/shim/frame-data/index.js +224 -0
  170. package/dist/shim/index.js +67 -0
  171. package/dist/shim/notebook/canvas-guard.js +82 -0
  172. package/dist/shim/notebook/catalog.js +35 -0
  173. package/dist/shim/notebook/format.js +736 -0
  174. package/dist/shim/notebook/frqtl-setups-importmap.js +81 -0
  175. package/dist/shim/notebook/kernel.js +671 -0
  176. package/dist/shim/notebook/knob.js +150 -0
  177. package/dist/shim/notebook/module-resolver.js +260 -0
  178. package/dist/shim/notebook/notebook-knobs.js +65 -0
  179. package/dist/shim/notebook/produced-bindings.js +67 -0
  180. package/dist/shim/notebook/realm-registry.js +121 -0
  181. package/dist/shim/notebook/realm-transport.js +73 -0
  182. package/dist/shim/notebook/setup-builder.js +507 -0
  183. package/dist/shim/notebook/store.js +159 -0
  184. package/dist/shim/notebook/vendor-importmap.js +134 -0
  185. package/dist/shim/notebook/view-builder.js +189 -0
  186. package/dist/shim/notebook/view-ctx.js +100 -0
  187. package/dist/shim/notebook/view-handle.js +202 -0
  188. package/dist/shim/notebook/warmup.js +102 -0
  189. package/dist/shim/package.json +4 -0
  190. package/dist/shim/page-params/index.js +213 -0
  191. package/dist/shim/project/identity.js +112 -0
  192. package/dist/shim/project/index.js +19 -0
  193. package/dist/shim/project/link.js +402 -0
  194. package/dist/shim/project/node-specifier-hook.mjs +35 -0
  195. package/dist/shim/project/register-node.mjs +17 -0
  196. package/dist/shim/project/specifiers.js +106 -0
  197. package/dist/shim/registry/copy-gate.js +110 -0
  198. package/dist/shim/registry/exhibits.js +455 -0
  199. package/dist/shim/registry/experiments.js +215 -0
  200. package/dist/shim/registry/index.js +19 -0
  201. package/dist/shim/registry/scene-source.js +157 -0
  202. package/dist/shim/registry/walkthrough.js +804 -0
  203. package/dist/shim/render/capture-envelope.js +279 -0
  204. package/dist/shim/render/heat-colormap.js +92 -0
  205. package/dist/shim/render/render-stages.js +855 -0
  206. package/dist/shim/render/sun-look.js +33 -0
  207. package/dist/shim/render-state/apply.js +262 -0
  208. package/dist/shim/render-state/index.js +74 -0
  209. package/dist/shim/rpc/index.js +207 -0
  210. package/dist/shim/runtime/index.js +1023 -0
  211. package/dist/shim/storage/boot.js +69 -0
  212. package/dist/shim/storage/index.js +169 -0
  213. package/dist/shim/storage/local-storage-provider.js +230 -0
  214. package/dist/shim/storage/make-namespace-store.js +132 -0
  215. package/dist/shim/storage/modules.js +249 -0
  216. package/dist/shim/storage/projects.js +150 -0
  217. package/llms.txt +12 -0
  218. package/package.json +63 -4
@@ -0,0 +1,279 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * capture-envelope.js — the TRANSPORT contract for a capture leaving this process.
5
+ *
6
+ * ── What it packages ─────────────────────────────────────────────────────────
7
+ *
8
+ * A still that stays in-process is `normalizeStill`'s bag and nothing more. A capture that
9
+ * CROSSES a boundary — to a model, an app microservice, a corpus row — needs two things the
10
+ * bag deliberately does not carry: the ENCODED bytes (rgba8 is not transportable), and, for
11
+ * a sequence, the map from frame index back to simulation state.
12
+ *
13
+ * This module adds exactly that envelope and NOTHING else.
14
+ *
15
+ * ── Why it composes rather than re-declares ──────────────────────────────────
16
+ *
17
+ * `render-stages.js` in this same directory OWNS the still schema: it holds `SCHEMA_VERSION`,
18
+ * refuses an unknown stage, refuses a `format` disagreeing with its stage, refuses an empty
19
+ * payload, refuses a still with no `scene` hash, and snapshots `data` so a live reference can
20
+ * never leak into a corpus. Re-listing `{stage, format, width, height, data, tick, scene}`
21
+ * here would be a hand mirror of that schema and would drift the first time a stage is added
22
+ * — the review-blocking bug class this codebase names by hand. So the still rides WHOLE,
23
+ * under one key, normalized by its owner, and this file never reads inside it except to
24
+ * cross-check what the encoder claims.
25
+ *
26
+ * ── The two forms ────────────────────────────────────────────────────────────
27
+ *
28
+ * stillEnvelope({ still, encoded }) one capture
29
+ * videoEnvelope({ track, encoded, truncated }) a sequence + its sidecar manifest
30
+ *
31
+ * The still envelope carries NO pose of its own: `normalizeStill` already holds `pose`, and a
32
+ * second one here would be a duplicated owner field. The VIDEO track carries pose per frame,
33
+ * because the owner has no notion of a sequence and nothing there could hold it.
34
+ *
35
+ * ── The encoded half: ONE key table per form, bytes always base64 ─────────────
36
+ *
37
+ * `ENCODED_KEYS[kind]` is the whole encoded shape of each form and the normalizer BUILDS its
38
+ * output by iterating it, so a declared field cannot be silently dropped on the way through.
39
+ * `bytes` is a base64 STRING in every host — the envelope is the transport form, and a string
40
+ * crosses a JSON boundary (a browser-automation `evaluate`, an HTTP body) unchanged. A video
41
+ * additionally REQUIRES `codec` (the codec string the encoder actually PRODUCED, never the
42
+ * request) and `muxer` (the pinned muxer that wrote the container). `ENCODERS` is the table of
43
+ * producers this contract recognises, each with the form and mime it produces.
44
+ *
45
+ * ── Why `truncated` has NO default ───────────────────────────────────────────
46
+ *
47
+ * `FrqtlVision.record` stops at its `maxFrames` bound and reports it as `truncated: true`,
48
+ * because "a truncated sequence that LOOKS complete is worse than a refusal". Defaulting
49
+ * `truncated` to false here would re-create that lie one layer up: a caller who forgot to
50
+ * ferry the flag would ship a short recording carrying an explicit claim of completeness. So it
51
+ * is REQUIRED, and a missing one throws.
52
+ *
53
+ * ── Why frame cadence is checked against the declared cadence ────────────────
54
+ *
55
+ * `ticksPerFrame` is the CAPTURE cadence (a tick IS the frame); `fps` is a PLAYBACK choice
56
+ * with no physical meaning — engine speed is 1 px/tick, so a presentation rate says nothing
57
+ * about the simulation. Both are required because collapsing them destroys the tick index,
58
+ * and the tick index is the only thing that makes a frame comparable to engine state. The
59
+ * frames' own tick deltas must equal the declared cadence: a manifest whose label disagrees
60
+ * with its data is worse than an unlabelled one, because it will be believed.
61
+ */
62
+
63
+ import * as RenderStages from './render-stages.js';
64
+
65
+
66
+
67
+ /** Envelope schema version. Independent of the still's own `v`, which its owner sets. */
68
+ var ENVELOPE_VERSION = 1;
69
+
70
+ /**
71
+ * The encoded shape of each envelope form. `encoder` is optional (null when absent) and
72
+ * validated against `ENCODERS` when present; every other key is a REQUIRED non-empty string.
73
+ */
74
+ var ENCODED_KEYS = {
75
+ still: ['mime', 'bytes', 'encoder'],
76
+ video: ['mime', 'bytes', 'encoder', 'codec', 'muxer']
77
+ };
78
+
79
+ /** The mime family each form accepts. */
80
+ var MIME_PREFIX = { still: 'image/', video: 'video/' };
81
+
82
+ /** The producers this contract recognises: the form each produces and its mime. */
83
+ var ENCODERS = {
84
+ 'FrqtlVision.toPNG': { kind: 'still', mime: 'image/png' },
85
+ 'FrqtlVision.toDataURL': { kind: 'still', mime: 'image/png' },
86
+ 'FrqtlVision.record': { kind: 'video', mime: 'video/mp4' }
87
+ };
88
+
89
+ function _fail(msg) { throw new Error('[capture-envelope] ' + msg + ' (FAIL-LOUD.)'); }
90
+
91
+ function _requireBool(value, name) {
92
+ if (typeof value !== 'boolean') {
93
+ _fail(name + ' must be an explicit boolean, got ' + String(value) +
94
+ ' — it is never defaulted, because a fabricated completeness claim is the ' +
95
+ 'exact failure the recorder\'s own bound reporting exists to prevent');
96
+ }
97
+ return value;
98
+ }
99
+
100
+ function _requirePosInt(value, name) {
101
+ if (typeof value !== 'number' || !isFinite(value) || value <= 0 || value % 1 !== 0) {
102
+ _fail(name + ' must be a positive whole number, got ' + String(value));
103
+ }
104
+ return value;
105
+ }
106
+
107
+ function _requireNonEmptyString(value, name, why) {
108
+ if (typeof value !== 'string' || !value) { _fail(name + ' must be a non-empty string' + (why ? ' — ' + why : '') + ', got ' + String(value)); }
109
+ return value;
110
+ }
111
+
112
+ /** Validate the encoded half of a `kind` envelope, building the output off `ENCODED_KEYS`. */
113
+ function _normalizeEncoded(encoded, kind) {
114
+ if (!encoded || typeof encoded !== 'object') { _fail('`encoded` must be an object carrying the transportable bytes'); }
115
+ var out = {};
116
+ var keys = ENCODED_KEYS[kind];
117
+ for (var i = 0; i < keys.length; i++) {
118
+ var k = keys[i];
119
+ if (k === 'encoder') {
120
+ out.encoder = (encoded.encoder === undefined) ? null : encoded.encoder;
121
+ } else if (k === 'bytes') {
122
+ if (typeof encoded.bytes !== 'string' || !encoded.bytes) {
123
+ _fail('encoded.bytes must be a non-empty base64 string — the envelope carries ' +
124
+ 'TRANSPORTABLE bytes; an rgba8 payload does not cross a boundary and the ' +
125
+ 'still already holds it');
126
+ }
127
+ out.bytes = encoded.bytes;
128
+ } else {
129
+ out[k] = _requireNonEmptyString(encoded[k], 'encoded.' + k);
130
+ }
131
+ }
132
+ if (out.mime.indexOf(MIME_PREFIX[kind]) !== 0) {
133
+ _fail('encoded.mime ' + JSON.stringify(out.mime) + ' is not a ' + MIME_PREFIX[kind] + '* type');
134
+ }
135
+ if (out.encoder !== null) {
136
+ if (!Object.prototype.hasOwnProperty.call(ENCODERS, out.encoder)) {
137
+ _fail('unknown encoder ' + JSON.stringify(out.encoder) + ' — known: ' +
138
+ Object.keys(ENCODERS).join(', '));
139
+ }
140
+ var known = ENCODERS[out.encoder];
141
+ if (known.kind !== kind) {
142
+ _fail('encoder ' + JSON.stringify(out.encoder) + ' produces a ' + known.kind +
143
+ ', not a ' + kind);
144
+ }
145
+ if (known.mime !== out.mime) {
146
+ _fail('encoder ' + JSON.stringify(out.encoder) + ' produces ' +
147
+ JSON.stringify(known.mime) + ', but encoded.mime says ' + JSON.stringify(out.mime));
148
+ }
149
+ }
150
+ return out;
151
+ }
152
+
153
+ /**
154
+ * bytesToBase64(bytes) — THE transportable-bytes encoding, in every host.
155
+ *
156
+ * A typed array → a base64 string, chunked (a per-byte concat of megabytes is slow, and
157
+ * `String.fromCharCode.apply` has an argument limit). The one encoder of `encoded.bytes`, and
158
+ * of the capture host's in-page payload: both read it from here, never from a copy.
159
+ *
160
+ * @param {ArrayBufferView} bytes
161
+ * @returns {string}
162
+ */
163
+ function bytesToBase64(bytes) {
164
+ if (!bytes || typeof bytes.BYTES_PER_ELEMENT !== 'number') {
165
+ _fail('bytesToBase64 expects a typed array, got ' + String(bytes));
166
+ }
167
+ if (typeof btoa !== 'function') {
168
+ _fail('bytesToBase64 needs a global btoa (every browser, and node >= 16)');
169
+ }
170
+ var u8 = new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
171
+ var bin = '';
172
+ for (var i = 0; i < u8.length; i += 0x8000) {
173
+ bin += String.fromCharCode.apply(null, u8.subarray(i, i + 0x8000));
174
+ }
175
+ return btoa(bin);
176
+ }
177
+
178
+ /**
179
+ * Compose a single-capture envelope.
180
+ *
181
+ * @param {{still:object, encoded:{mime:string,bytes:string,encoder?:string}}} partial
182
+ * `still` is a RAW partial bag; it is normalized HERE by its owner, never pre-normalized
183
+ * by the caller (that would let two callers disagree about who validated).
184
+ * @returns {object} the envelope
185
+ */
186
+ function stillEnvelope(partial) {
187
+ if (!partial || typeof partial !== 'object') { _fail('expected an object, got ' + String(partial)); }
188
+
189
+ var still = RenderStages.normalizeStill(partial.still);
190
+ var encoded = _normalizeEncoded(partial.encoded, 'still');
191
+
192
+ return {
193
+ kind: 'still',
194
+ v: ENVELOPE_VERSION,
195
+ still: still,
196
+ encoded: encoded
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Compose a sequence envelope. `track` is the SIDECAR MANIFEST — without it the bytes are an
202
+ * unlabelled movie and no frame can be aligned to the state that produced it.
203
+ *
204
+ * @param {{track:object, encoded:{mime:string,bytes:string,codec:string,muxer:string,encoder?:string},
205
+ * truncated:boolean}} partial
206
+ * @returns {object} the envelope
207
+ */
208
+ function videoEnvelope(partial) {
209
+ if (!partial || typeof partial !== 'object') { _fail('expected an object, got ' + String(partial)); }
210
+
211
+ var t = partial.track;
212
+ if (!t || typeof t !== 'object') { _fail('`track` (the sidecar manifest) is REQUIRED — without it the bytes are an unlabelled movie'); }
213
+
214
+ _requireNonEmptyString(t.scene, 'track.scene',
215
+ 'a sequence without a scene id is unmoored exactly as a still without one is');
216
+ var stage = RenderStages.assertStage(t.stage);
217
+
218
+ var width = _requirePosInt(t.width, 'track.width');
219
+ var height = _requirePosInt(t.height, 'track.height');
220
+ var ticksPerFrame = _requirePosInt(t.ticksPerFrame, 'track.ticksPerFrame');
221
+ var fps = _requirePosInt(t.fps, 'track.fps');
222
+
223
+ var frames = t.frames;
224
+ if (!Array.isArray(frames) || frames.length === 0) {
225
+ _fail('track.frames must be a non-empty array of {i, tick, pose} — an empty manifest ' +
226
+ 'over real bytes is a measurement built on nothing');
227
+ }
228
+
229
+ for (var n = 0; n < frames.length; n++) {
230
+ var f = frames[n];
231
+ if (!f || typeof f !== 'object') { _fail('track.frames[' + n + '] must be an object'); }
232
+ if (f.i !== n) {
233
+ _fail('track.frames[' + n + '].i is ' + String(f.i) + ' — frame indices must be ' +
234
+ 'contiguous from 0, or the map from bytes to state is guesswork');
235
+ }
236
+ if (typeof f.tick !== 'number' || !isFinite(f.tick) || f.tick % 1 !== 0) {
237
+ _fail('track.frames[' + n + '].tick must be a whole number, got ' + String(f.tick));
238
+ }
239
+ if (n > 0) {
240
+ var delta = f.tick - frames[n - 1].tick;
241
+ if (delta !== ticksPerFrame) {
242
+ _fail('track.frames[' + n + '] is ' + delta + ' ticks after its predecessor, but ' +
243
+ 'track.ticksPerFrame declares ' + ticksPerFrame + ' — a manifest whose ' +
244
+ 'label disagrees with its data is worse than none, because it is believed');
245
+ }
246
+ }
247
+ }
248
+
249
+ var encoded = _normalizeEncoded(partial.encoded, 'video');
250
+
251
+ return {
252
+ kind: 'video',
253
+ v: ENVELOPE_VERSION,
254
+ track: {
255
+ scene: t.scene,
256
+ stage: stage,
257
+ width: width,
258
+ height: height,
259
+ ticksPerFrame: ticksPerFrame,
260
+ fps: fps,
261
+ firstTick: frames[0].tick,
262
+ lastTick: frames[frames.length - 1].tick,
263
+ frames: frames.map(function (f) {
264
+ return { i: f.i, tick: f.tick, pose: f.pose === undefined ? null : f.pose };
265
+ })
266
+ },
267
+ encoded: encoded,
268
+ truncated: _requireBool(partial.truncated, 'truncated')
269
+ };
270
+ }
271
+
272
+ export {
273
+ ENVELOPE_VERSION,
274
+ ENCODED_KEYS,
275
+ ENCODERS,
276
+ bytesToBase64,
277
+ stillEnvelope,
278
+ videoEnvelope
279
+ };
@@ -0,0 +1,92 @@
1
+ /* Copyright 2023-2026 by Essam Abadir */
2
+
3
+ /**
4
+ * lib/shim/render/heat-colormap — the ONE count→intensity colour ramp.
5
+ *
6
+ * Peer of `lib/shim/frame-data` (the data-bubble/render-intent contract) and
7
+ * `lib/shim/rpc` (the call() primitive): named ES-module exports, imported BOTH
8
+ * - by the game layer (`lib/frqtl/parts/GraphingDetectorWall.js`'s heat
9
+ * painter), and
10
+ * - by a component above the shim (`lib/components/notebook-pane/realm-chart.js`).
11
+ * Both arrows are legal; neither consumer owns the ramp.
12
+ *
13
+ * ── Why it lives here, and NOT in LightColor.js ──────────────────────────────
14
+ *
15
+ * `lib/frqtl/parts/LightColor.js` owns FREQUENCY→light-colour
16
+ * semantics (ROYGBIV, derived from a leaf's fullness). A count→intensity heat
17
+ * ramp is a different vocabulary entirely — it colours a STATISTIC (how many
18
+ * hits landed in a bin), not a particle's frequency band. Housing it in
19
+ * LightColor would be dishonest labelling; housing a private copy in each
20
+ * consumer is the schema-mirror-drift anti-pattern (the tell: the in-scene
21
+ * detector heatmap and the DOM topology chart, reading the SAME grid, drifting
22
+ * to different colours).
23
+ *
24
+ * ── Layer 1: API contract ───────────────────────────────────────────────────
25
+ *
26
+ * HEAT_STOPS — the frozen [r,g,b] control points (deep blue → cyan →
27
+ * green → yellow → red, a standard topographic ramp).
28
+ * heatColor(t) — normalized intensity t (0..1, clamped) → an `rgb(r,g,b)`
29
+ * CSS string (a canvas `fillStyle` / Chart.js colour).
30
+ * heatRgb(t) — the same sample as an [r,g,b] triple (0-255 integers),
31
+ * for a consumer that needs the channels (e.g. compositing
32
+ * with its own alpha).
33
+ *
34
+ * ── Layer 2: physics behaviour ──────────────────────────────────────────────
35
+ *
36
+ * ZERO physics — this is an output-boundary presentation map over an
37
+ * already-collected statistic. It never reads a frame, never draws from the
38
+ * engine RNG, and cannot move a canonical-bench golden (SU1).
39
+ */
40
+
41
+
42
+
43
+ /**
44
+ * The topographic control points: deep blue → cyan → green → yellow → red.
45
+ * Exported so a conformance/drift test can assert both consumers resolve the
46
+ * SAME ramp instead of hand-copying five triples into a test file.
47
+ */
48
+ var HEAT_STOPS = Object.freeze([
49
+ Object.freeze([12, 24, 70]),
50
+ Object.freeze([20, 130, 190]),
51
+ Object.freeze([40, 190, 120]),
52
+ Object.freeze([235, 210, 60]),
53
+ Object.freeze([235, 70, 45])
54
+ ]);
55
+
56
+ /**
57
+ * Sample the ramp at normalized intensity `t`.
58
+ * @param {number} t — 0..1 (clamped; a non-finite input samples the low end).
59
+ * @returns {Array<number>} [r, g, b], each a 0-255 integer.
60
+ */
61
+ function heatRgb(t) {
62
+ var n = Number(t);
63
+ if (!isFinite(n)) { n = 0; }
64
+ var u = n < 0 ? 0 : (n > 1 ? 1 : n);
65
+ var f = u * (HEAT_STOPS.length - 1);
66
+ var i = Math.min(HEAT_STOPS.length - 2, Math.floor(f));
67
+ var k = f - i;
68
+ var a = HEAT_STOPS[i], b = HEAT_STOPS[i + 1];
69
+ return [
70
+ Math.round(a[0] + (b[0] - a[0]) * k),
71
+ Math.round(a[1] + (b[1] - a[1]) * k),
72
+ Math.round(a[2] + (b[2] - a[2]) * k)
73
+ ];
74
+ }
75
+
76
+ /**
77
+ * Sample the ramp at normalized intensity `t` as a CSS colour string.
78
+ * @param {number} t — 0..1 (clamped).
79
+ * @returns {string} `rgb(r,g,b)`
80
+ */
81
+ function heatColor(t) {
82
+ var c = heatRgb(t);
83
+ return 'rgb(' + c[0] + ',' + c[1] + ',' + c[2] + ')';
84
+ }
85
+
86
+ // ── Export ────────────────────────────────────────────────────────────────────
87
+
88
+ export {
89
+ HEAT_STOPS,
90
+ heatRgb,
91
+ heatColor
92
+ };