@fundamental-engine/core 0.4.0

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 (371) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +128 -0
  3. package/dist/agents/element-agent.d.ts +38 -0
  4. package/dist/agents/element-agent.d.ts.map +1 -0
  5. package/dist/agents/element-agent.js +70 -0
  6. package/dist/agents/element-agent.js.map +1 -0
  7. package/dist/agents/event-agent.d.ts +47 -0
  8. package/dist/agents/event-agent.d.ts.map +1 -0
  9. package/dist/agents/event-agent.js +82 -0
  10. package/dist/agents/event-agent.js.map +1 -0
  11. package/dist/agents/index.d.ts +17 -0
  12. package/dist/agents/index.d.ts.map +1 -0
  13. package/dist/agents/index.js +57 -0
  14. package/dist/agents/index.js.map +1 -0
  15. package/dist/agents/region-agents.d.ts +40 -0
  16. package/dist/agents/region-agents.d.ts.map +1 -0
  17. package/dist/agents/region-agents.js +22 -0
  18. package/dist/agents/region-agents.js.map +1 -0
  19. package/dist/agents/relationship.d.ts +55 -0
  20. package/dist/agents/relationship.d.ts.map +1 -0
  21. package/dist/agents/relationship.js +40 -0
  22. package/dist/agents/relationship.js.map +1 -0
  23. package/dist/agents/user-agent.d.ts +57 -0
  24. package/dist/agents/user-agent.d.ts.map +1 -0
  25. package/dist/agents/user-agent.js +45 -0
  26. package/dist/agents/user-agent.js.map +1 -0
  27. package/dist/config/forces.config.d.ts +101 -0
  28. package/dist/config/forces.config.d.ts.map +1 -0
  29. package/dist/config/forces.config.js +239 -0
  30. package/dist/config/forces.config.js.map +1 -0
  31. package/dist/config/manual.d.ts +134 -0
  32. package/dist/config/manual.d.ts.map +1 -0
  33. package/dist/config/manual.js +604 -0
  34. package/dist/config/manual.js.map +1 -0
  35. package/dist/config/palettes.d.ts +18 -0
  36. package/dist/config/palettes.d.ts.map +1 -0
  37. package/dist/config/palettes.js +34 -0
  38. package/dist/config/palettes.js.map +1 -0
  39. package/dist/config/presets.d.ts +48 -0
  40. package/dist/config/presets.d.ts.map +1 -0
  41. package/dist/config/presets.js +87 -0
  42. package/dist/config/presets.js.map +1 -0
  43. package/dist/config/tokens.d.ts +3 -0
  44. package/dist/config/tokens.d.ts.map +1 -0
  45. package/dist/config/tokens.js +16 -0
  46. package/dist/config/tokens.js.map +1 -0
  47. package/dist/conformance/expectations.d.ts +40 -0
  48. package/dist/conformance/expectations.d.ts.map +1 -0
  49. package/dist/conformance/expectations.js +347 -0
  50. package/dist/conformance/expectations.js.map +1 -0
  51. package/dist/conformance/experiments.d.ts +17 -0
  52. package/dist/conformance/experiments.d.ts.map +1 -0
  53. package/dist/conformance/experiments.js +875 -0
  54. package/dist/conformance/experiments.js.map +1 -0
  55. package/dist/conformance/run.d.ts +18 -0
  56. package/dist/conformance/run.d.ts.map +1 -0
  57. package/dist/conformance/run.js +240 -0
  58. package/dist/conformance/run.js.map +1 -0
  59. package/dist/conformance/types.d.ts +100 -0
  60. package/dist/conformance/types.d.ts.map +1 -0
  61. package/dist/conformance/types.js +2 -0
  62. package/dist/conformance/types.js.map +1 -0
  63. package/dist/contracts/guards.d.ts +51 -0
  64. package/dist/contracts/guards.d.ts.map +1 -0
  65. package/dist/contracts/guards.js +100 -0
  66. package/dist/contracts/guards.js.map +1 -0
  67. package/dist/contracts/index.d.ts +18 -0
  68. package/dist/contracts/index.d.ts.map +1 -0
  69. package/dist/contracts/index.js +107 -0
  70. package/dist/contracts/index.js.map +1 -0
  71. package/dist/contracts/passport.d.ts +88 -0
  72. package/dist/contracts/passport.d.ts.map +1 -0
  73. package/dist/contracts/passport.js +135 -0
  74. package/dist/contracts/passport.js.map +1 -0
  75. package/dist/contracts/types.d.ts +120 -0
  76. package/dist/contracts/types.d.ts.map +1 -0
  77. package/dist/contracts/types.js +24 -0
  78. package/dist/contracts/types.js.map +1 -0
  79. package/dist/core/accretion.d.ts +50 -0
  80. package/dist/core/accretion.d.ts.map +1 -0
  81. package/dist/core/accretion.js +98 -0
  82. package/dist/core/accretion.js.map +1 -0
  83. package/dist/core/agents.d.ts +31 -0
  84. package/dist/core/agents.d.ts.map +1 -0
  85. package/dist/core/agents.js +51 -0
  86. package/dist/core/agents.js.map +1 -0
  87. package/dist/core/attention.d.ts +72 -0
  88. package/dist/core/attention.d.ts.map +1 -0
  89. package/dist/core/attention.js +122 -0
  90. package/dist/core/attention.js.map +1 -0
  91. package/dist/core/causality.d.ts +38 -0
  92. package/dist/core/causality.d.ts.map +1 -0
  93. package/dist/core/causality.js +64 -0
  94. package/dist/core/causality.js.map +1 -0
  95. package/dist/core/conditions.d.ts +10 -0
  96. package/dist/core/conditions.d.ts.map +1 -0
  97. package/dist/core/conditions.js +22 -0
  98. package/dist/core/conditions.js.map +1 -0
  99. package/dist/core/currents.d.ts +53 -0
  100. package/dist/core/currents.d.ts.map +1 -0
  101. package/dist/core/currents.js +65 -0
  102. package/dist/core/currents.js.map +1 -0
  103. package/dist/core/dock.d.ts +35 -0
  104. package/dist/core/dock.d.ts.map +1 -0
  105. package/dist/core/dock.js +39 -0
  106. package/dist/core/dock.js.map +1 -0
  107. package/dist/core/events.d.ts +23 -0
  108. package/dist/core/events.d.ts.map +1 -0
  109. package/dist/core/events.js +34 -0
  110. package/dist/core/events.js.map +1 -0
  111. package/dist/core/feedback-sink.d.ts +32 -0
  112. package/dist/core/feedback-sink.d.ts.map +1 -0
  113. package/dist/core/feedback-sink.js +53 -0
  114. package/dist/core/feedback-sink.js.map +1 -0
  115. package/dist/core/feedback.d.ts +11 -0
  116. package/dist/core/feedback.d.ts.map +1 -0
  117. package/dist/core/feedback.js +16 -0
  118. package/dist/core/feedback.js.map +1 -0
  119. package/dist/core/field-store.d.ts +26 -0
  120. package/dist/core/field-store.d.ts.map +1 -0
  121. package/dist/core/field-store.js +54 -0
  122. package/dist/core/field-store.js.map +1 -0
  123. package/dist/core/field.d.ts +18 -0
  124. package/dist/core/field.d.ts.map +1 -0
  125. package/dist/core/field.js +1943 -0
  126. package/dist/core/field.js.map +1 -0
  127. package/dist/core/fieldline-seeds.d.ts +25 -0
  128. package/dist/core/fieldline-seeds.d.ts.map +1 -0
  129. package/dist/core/fieldline-seeds.js +32 -0
  130. package/dist/core/fieldline-seeds.js.map +1 -0
  131. package/dist/core/fieldlines.d.ts +75 -0
  132. package/dist/core/fieldlines.d.ts.map +1 -0
  133. package/dist/core/fieldlines.js +111 -0
  134. package/dist/core/fieldlines.js.map +1 -0
  135. package/dist/core/flow.d.ts +38 -0
  136. package/dist/core/flow.d.ts.map +1 -0
  137. package/dist/core/flow.js +27 -0
  138. package/dist/core/flow.js.map +1 -0
  139. package/dist/core/formations.d.ts +11 -0
  140. package/dist/core/formations.d.ts.map +1 -0
  141. package/dist/core/formations.js +22 -0
  142. package/dist/core/formations.js.map +1 -0
  143. package/dist/core/geometry.d.ts +67 -0
  144. package/dist/core/geometry.d.ts.map +1 -0
  145. package/dist/core/geometry.js +68 -0
  146. package/dist/core/geometry.js.map +1 -0
  147. package/dist/core/heatmap.d.ts +22 -0
  148. package/dist/core/heatmap.d.ts.map +1 -0
  149. package/dist/core/heatmap.js +55 -0
  150. package/dist/core/heatmap.js.map +1 -0
  151. package/dist/core/host.d.ts +46 -0
  152. package/dist/core/host.d.ts.map +1 -0
  153. package/dist/core/host.js +11 -0
  154. package/dist/core/host.js.map +1 -0
  155. package/dist/core/integrator.d.ts +24 -0
  156. package/dist/core/integrator.d.ts.map +1 -0
  157. package/dist/core/integrator.js +375 -0
  158. package/dist/core/integrator.js.map +1 -0
  159. package/dist/core/math.d.ts +37 -0
  160. package/dist/core/math.d.ts.map +1 -0
  161. package/dist/core/math.js +77 -0
  162. package/dist/core/math.js.map +1 -0
  163. package/dist/core/reactions.d.ts +32 -0
  164. package/dist/core/reactions.d.ts.map +1 -0
  165. package/dist/core/reactions.js +45 -0
  166. package/dist/core/reactions.js.map +1 -0
  167. package/dist/core/registry.d.ts +13 -0
  168. package/dist/core/registry.d.ts.map +1 -0
  169. package/dist/core/registry.js +20 -0
  170. package/dist/core/registry.js.map +1 -0
  171. package/dist/core/render-backend.d.ts +46 -0
  172. package/dist/core/render-backend.d.ts.map +1 -0
  173. package/dist/core/render-backend.js +75 -0
  174. package/dist/core/render-backend.js.map +1 -0
  175. package/dist/core/render-modes.d.ts +42 -0
  176. package/dist/core/render-modes.d.ts.map +1 -0
  177. package/dist/core/render-modes.js +141 -0
  178. package/dist/core/render-modes.js.map +1 -0
  179. package/dist/core/reservoir.d.ts +43 -0
  180. package/dist/core/reservoir.d.ts.map +1 -0
  181. package/dist/core/reservoir.js +207 -0
  182. package/dist/core/reservoir.js.map +1 -0
  183. package/dist/core/scalar-grid.d.ts +51 -0
  184. package/dist/core/scalar-grid.d.ts.map +1 -0
  185. package/dist/core/scalar-grid.js +146 -0
  186. package/dist/core/scalar-grid.js.map +1 -0
  187. package/dist/core/scanner.d.ts +59 -0
  188. package/dist/core/scanner.d.ts.map +1 -0
  189. package/dist/core/scanner.js +260 -0
  190. package/dist/core/scanner.js.map +1 -0
  191. package/dist/core/shadow.d.ts +69 -0
  192. package/dist/core/shadow.d.ts.map +1 -0
  193. package/dist/core/shadow.js +84 -0
  194. package/dist/core/shadow.js.map +1 -0
  195. package/dist/core/spatial-hash.d.ts +30 -0
  196. package/dist/core/spatial-hash.d.ts.map +1 -0
  197. package/dist/core/spatial-hash.js +64 -0
  198. package/dist/core/spatial-hash.js.map +1 -0
  199. package/dist/core/streamlines.d.ts +29 -0
  200. package/dist/core/streamlines.d.ts.map +1 -0
  201. package/dist/core/streamlines.js +70 -0
  202. package/dist/core/streamlines.js.map +1 -0
  203. package/dist/core/surface.d.ts +19 -0
  204. package/dist/core/surface.d.ts.map +1 -0
  205. package/dist/core/surface.js +21 -0
  206. package/dist/core/surface.js.map +1 -0
  207. package/dist/core/temporal.d.ts +110 -0
  208. package/dist/core/temporal.d.ts.map +1 -0
  209. package/dist/core/temporal.js +139 -0
  210. package/dist/core/temporal.js.map +1 -0
  211. package/dist/core/thermo.d.ts +48 -0
  212. package/dist/core/thermo.d.ts.map +1 -0
  213. package/dist/core/thermo.js +48 -0
  214. package/dist/core/thermo.js.map +1 -0
  215. package/dist/core/types.d.ts +610 -0
  216. package/dist/core/types.d.ts.map +1 -0
  217. package/dist/core/types.js +2 -0
  218. package/dist/core/types.js.map +1 -0
  219. package/dist/core/weights.d.ts +111 -0
  220. package/dist/core/weights.d.ts.map +1 -0
  221. package/dist/core/weights.js +128 -0
  222. package/dist/core/weights.js.map +1 -0
  223. package/dist/diagnostics/energy.d.ts +21 -0
  224. package/dist/diagnostics/energy.d.ts.map +1 -0
  225. package/dist/diagnostics/energy.js +27 -0
  226. package/dist/diagnostics/energy.js.map +1 -0
  227. package/dist/diagnostics/fields.d.ts +23 -0
  228. package/dist/diagnostics/fields.d.ts.map +1 -0
  229. package/dist/diagnostics/fields.js +30 -0
  230. package/dist/diagnostics/fields.js.map +1 -0
  231. package/dist/diagnostics/index.d.ts +46 -0
  232. package/dist/diagnostics/index.d.ts.map +1 -0
  233. package/dist/diagnostics/index.js +23 -0
  234. package/dist/diagnostics/index.js.map +1 -0
  235. package/dist/diagnostics/modes.d.ts +108 -0
  236. package/dist/diagnostics/modes.d.ts.map +1 -0
  237. package/dist/diagnostics/modes.js +181 -0
  238. package/dist/diagnostics/modes.js.map +1 -0
  239. package/dist/diagnostics/potential.d.ts +30 -0
  240. package/dist/diagnostics/potential.d.ts.map +1 -0
  241. package/dist/diagnostics/potential.js +43 -0
  242. package/dist/diagnostics/potential.js.map +1 -0
  243. package/dist/diagnostics/probes.d.ts +31 -0
  244. package/dist/diagnostics/probes.d.ts.map +1 -0
  245. package/dist/diagnostics/probes.js +61 -0
  246. package/dist/diagnostics/probes.js.map +1 -0
  247. package/dist/diagnostics/render.d.ts +49 -0
  248. package/dist/diagnostics/render.d.ts.map +1 -0
  249. package/dist/diagnostics/render.js +132 -0
  250. package/dist/diagnostics/render.js.map +1 -0
  251. package/dist/export.d.ts +18 -0
  252. package/dist/export.d.ts.map +1 -0
  253. package/dist/export.js +17 -0
  254. package/dist/export.js.map +1 -0
  255. package/dist/forces/extended.d.ts +121 -0
  256. package/dist/forces/extended.d.ts.map +1 -0
  257. package/dist/forces/extended.js +674 -0
  258. package/dist/forces/extended.js.map +1 -0
  259. package/dist/forces/index.d.ts +33 -0
  260. package/dist/forces/index.d.ts.map +1 -0
  261. package/dist/forces/index.js +237 -0
  262. package/dist/forces/index.js.map +1 -0
  263. package/dist/forces/natural.d.ts +106 -0
  264. package/dist/forces/natural.d.ts.map +1 -0
  265. package/dist/forces/natural.js +385 -0
  266. package/dist/forces/natural.js.map +1 -0
  267. package/dist/index.d.ts +59 -0
  268. package/dist/index.d.ts.map +1 -0
  269. package/dist/index.js +71 -0
  270. package/dist/index.js.map +1 -0
  271. package/dist/inspect/budget.d.ts +17 -0
  272. package/dist/inspect/budget.d.ts.map +1 -0
  273. package/dist/inspect/budget.js +19 -0
  274. package/dist/inspect/budget.js.map +1 -0
  275. package/dist/inspect/index.d.ts +10 -0
  276. package/dist/inspect/index.d.ts.map +1 -0
  277. package/dist/inspect/index.js +10 -0
  278. package/dist/inspect/index.js.map +1 -0
  279. package/dist/inspect/report.d.ts +17 -0
  280. package/dist/inspect/report.d.ts.map +1 -0
  281. package/dist/inspect/report.js +44 -0
  282. package/dist/inspect/report.js.map +1 -0
  283. package/dist/inspect/snapshot.d.ts +21 -0
  284. package/dist/inspect/snapshot.d.ts.map +1 -0
  285. package/dist/inspect/snapshot.js +30 -0
  286. package/dist/inspect/snapshot.js.map +1 -0
  287. package/dist/recipes/catalog.d.ts +51 -0
  288. package/dist/recipes/catalog.d.ts.map +1 -0
  289. package/dist/recipes/catalog.js +1496 -0
  290. package/dist/recipes/catalog.js.map +1 -0
  291. package/dist/recipes/charge.d.ts +18 -0
  292. package/dist/recipes/charge.d.ts.map +1 -0
  293. package/dist/recipes/charge.js +27 -0
  294. package/dist/recipes/charge.js.map +1 -0
  295. package/dist/recipes/compile.d.ts +93 -0
  296. package/dist/recipes/compile.d.ts.map +1 -0
  297. package/dist/recipes/compile.js +113 -0
  298. package/dist/recipes/compile.js.map +1 -0
  299. package/dist/recipes/explain.d.ts +8 -0
  300. package/dist/recipes/explain.d.ts.map +1 -0
  301. package/dist/recipes/explain.js +46 -0
  302. package/dist/recipes/explain.js.map +1 -0
  303. package/dist/recipes/gallery.d.ts +6 -0
  304. package/dist/recipes/gallery.d.ts.map +1 -0
  305. package/dist/recipes/gallery.js +6 -0
  306. package/dist/recipes/gallery.js.map +1 -0
  307. package/dist/recipes/gravity.d.ts +16 -0
  308. package/dist/recipes/gravity.d.ts.map +1 -0
  309. package/dist/recipes/gravity.js +27 -0
  310. package/dist/recipes/gravity.js.map +1 -0
  311. package/dist/recipes/index.d.ts +18 -0
  312. package/dist/recipes/index.d.ts.map +1 -0
  313. package/dist/recipes/index.js +36 -0
  314. package/dist/recipes/index.js.map +1 -0
  315. package/dist/recipes/intent.d.ts +44 -0
  316. package/dist/recipes/intent.d.ts.map +1 -0
  317. package/dist/recipes/intent.js +46 -0
  318. package/dist/recipes/intent.js.map +1 -0
  319. package/dist/recipes/schema.d.ts +103 -0
  320. package/dist/recipes/schema.d.ts.map +1 -0
  321. package/dist/recipes/schema.js +123 -0
  322. package/dist/recipes/schema.js.map +1 -0
  323. package/dist/recipes/wayfinding.d.ts +39 -0
  324. package/dist/recipes/wayfinding.d.ts.map +1 -0
  325. package/dist/recipes/wayfinding.js +77 -0
  326. package/dist/recipes/wayfinding.js.map +1 -0
  327. package/dist/semantic/index.d.ts +13 -0
  328. package/dist/semantic/index.d.ts.map +1 -0
  329. package/dist/semantic/index.js +31 -0
  330. package/dist/semantic/index.js.map +1 -0
  331. package/dist/semantic/layers.d.ts +24 -0
  332. package/dist/semantic/layers.d.ts.map +1 -0
  333. package/dist/semantic/layers.js +27 -0
  334. package/dist/semantic/layers.js.map +1 -0
  335. package/dist/semantic/materials.d.ts +20 -0
  336. package/dist/semantic/materials.d.ts.map +1 -0
  337. package/dist/semantic/materials.js +17 -0
  338. package/dist/semantic/materials.js.map +1 -0
  339. package/dist/semantic/states.d.ts +11 -0
  340. package/dist/semantic/states.d.ts.map +1 -0
  341. package/dist/semantic/states.js +26 -0
  342. package/dist/semantic/states.js.map +1 -0
  343. package/dist/visual/channels.d.ts +71 -0
  344. package/dist/visual/channels.d.ts.map +1 -0
  345. package/dist/visual/channels.js +70 -0
  346. package/dist/visual/channels.js.map +1 -0
  347. package/dist/visual/index.d.ts +39 -0
  348. package/dist/visual/index.d.ts.map +1 -0
  349. package/dist/visual/index.js +30 -0
  350. package/dist/visual/index.js.map +1 -0
  351. package/dist/visual/lint.d.ts +41 -0
  352. package/dist/visual/lint.d.ts.map +1 -0
  353. package/dist/visual/lint.js +58 -0
  354. package/dist/visual/lint.js.map +1 -0
  355. package/dist/visual/mapping.d.ts +13 -0
  356. package/dist/visual/mapping.d.ts.map +1 -0
  357. package/dist/visual/mapping.js +43 -0
  358. package/dist/visual/mapping.js.map +1 -0
  359. package/dist/visual/semantic-text.d.ts +28 -0
  360. package/dist/visual/semantic-text.d.ts.map +1 -0
  361. package/dist/visual/semantic-text.js +36 -0
  362. package/dist/visual/semantic-text.js.map +1 -0
  363. package/dist/visual/tokens.d.ts +23 -0
  364. package/dist/visual/tokens.d.ts.map +1 -0
  365. package/dist/visual/tokens.js +54 -0
  366. package/dist/visual/tokens.js.map +1 -0
  367. package/dist/visual/visualization.d.ts +31 -0
  368. package/dist/visual/visualization.d.ts.map +1 -0
  369. package/dist/visual/visualization.js +47 -0
  370. package/dist/visual/visualization.js.map +1 -0
  371. package/package.json +60 -0
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Accretion — the pure, DOM-free core of the sink submodel (§6.9, agent-consumption-model).
3
+ *
4
+ * A `sink` captures matter into an accretion core, HOLDS it (the particle stays in the pool with
5
+ * `cap = b`, drifting to the core in the integrator), and on saturation RELEASES exactly what it
6
+ * held. This module owns the conserved release math and the capture/release event edge so both
7
+ * `field.ts` (the impure shell that dispatches events + writes the DOM) and the conformance tests
8
+ * exercise the *same* code. No DOM, no globals.
9
+ */
10
+ import type { Body, Particle } from './types.ts';
11
+ /**
12
+ * Release exactly the particles a body captured: eject each just **past** the absorption radius
13
+ * along a random bearing, give it a radial outward velocity, clear its capture + heat to 1, and
14
+ * reset the body's load to 0. Held matter is **conserved** — released particles stay in the
15
+ * caller's pool (never deleted), so `count` is preserved. Returns the released particles (in pool
16
+ * order). `rng` is injectable for deterministic tests; defaults to `Math.random`.
17
+ *
18
+ * Ejecting past `absorbR` (rather than at the core) is what makes a supernova a real cycle. Matter
19
+ * dropped at the core sits *inside* the capture radius and is re-grabbed on the very next frame,
20
+ * degenerating the explosion into a per-frame strobe whose blast progressively evacuates the
21
+ * catchment until the sink falls dormant. Leaving the accretion zone lets a `sink+attract` well
22
+ * reel the ejecta back for a genuine fill → explode → fall-back → refill cycle (a lone `sink`
23
+ * simply lets it disperse).
24
+ */
25
+ export declare function releaseCaptured(particles: readonly Particle[], b: Body, rng?: () => number): Particle[];
26
+ /** Sink fill fraction ∈ [0,1] — the value written to `--load` / `--mass`. 0 when not a sink. */
27
+ export declare function sinkLoad(b: Body): number;
28
+ /** The discrete capture/release event a sink crosses this frame, if any. */
29
+ export type CaptureEvent = 'captured' | 'released' | null;
30
+ /**
31
+ * Capture/release event edge for a sink body. `prevArmed` is whether `captured` has fired since the
32
+ * last release; `accreting` is `b.accreted > 0` after this frame's force pass. Rising edge →
33
+ * `captured`; falling edge (which only happens via release/supernova, since load drops to 0 there)
34
+ * → `released`. Pure: the caller persists `armed` and performs the dispatch.
35
+ */
36
+ export declare function captureEdge(prevArmed: boolean, accreting: boolean): {
37
+ fire: CaptureEvent;
38
+ armed: boolean;
39
+ };
40
+ /**
41
+ * Attention-gated discharge (#365, the Contour Charge behavior): a sink gated on engagement
42
+ * (`data-when="active"`) releases what it holds on the FALLING edge of engagement — the vessel
43
+ * charges while attended and discharges when attention leaves. The condition pass already gates
44
+ * capture (a closed gate pulls nothing new in); this is the matching release side. Pure trigger:
45
+ * the caller supplies the release ritual (the engine passes `env.supernova`, so discharge is the
46
+ * same conserved release — same radial burst, same `field:released` event — as saturation).
47
+ * Returns the bodies that discharged this pass.
48
+ */
49
+ export declare function dischargeDisengaged(bodies: readonly Body[], release: (b: Body) => void): Body[];
50
+ //# sourceMappingURL=accretion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accretion.d.ts","sourceRoot":"","sources":["../../src/core/accretion.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,CAAC,EAAE,IAAI,EACP,GAAG,GAAE,MAAM,MAAoB,GAC9B,QAAQ,EAAE,CA4BZ;AAED,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAIxC;AAED,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,UAAU,GAAG,IAAI,CAAC;AAE1D;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAI1G;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI,EAAE,CAW/F"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Accretion — the pure, DOM-free core of the sink submodel (§6.9, agent-consumption-model).
3
+ *
4
+ * A `sink` captures matter into an accretion core, HOLDS it (the particle stays in the pool with
5
+ * `cap = b`, drifting to the core in the integrator), and on saturation RELEASES exactly what it
6
+ * held. This module owns the conserved release math and the capture/release event edge so both
7
+ * `field.ts` (the impure shell that dispatches events + writes the DOM) and the conformance tests
8
+ * exercise the *same* code. No DOM, no globals.
9
+ */
10
+ /**
11
+ * Release exactly the particles a body captured: eject each just **past** the absorption radius
12
+ * along a random bearing, give it a radial outward velocity, clear its capture + heat to 1, and
13
+ * reset the body's load to 0. Held matter is **conserved** — released particles stay in the
14
+ * caller's pool (never deleted), so `count` is preserved. Returns the released particles (in pool
15
+ * order). `rng` is injectable for deterministic tests; defaults to `Math.random`.
16
+ *
17
+ * Ejecting past `absorbR` (rather than at the core) is what makes a supernova a real cycle. Matter
18
+ * dropped at the core sits *inside* the capture radius and is re-grabbed on the very next frame,
19
+ * degenerating the explosion into a per-frame strobe whose blast progressively evacuates the
20
+ * catchment until the sink falls dormant. Leaving the accretion zone lets a `sink+attract` well
21
+ * reel the ejecta back for a genuine fill → explode → fall-back → refill cycle (a lone `sink`
22
+ * simply lets it disperse).
23
+ */
24
+ export function releaseCaptured(particles, b, rng = Math.random) {
25
+ const released = [];
26
+ const rim = b.absorbR + 6; // clear the capture horizon so it isn't re-captured next frame
27
+ for (const q of particles) {
28
+ if (q.cap !== b)
29
+ continue;
30
+ const ang = rng() * Math.PI * 2;
31
+ const spd = 4 + rng() * 3;
32
+ q.cap = null;
33
+ q.x = b.cx + Math.cos(ang) * rim;
34
+ q.y = b.cy + Math.sin(ang) * rim;
35
+ q.vx = Math.cos(ang) * spd;
36
+ q.vy = Math.sin(ang) * spd;
37
+ // the core sits on the page plane: release resets the z lane too (z-axis.md).
38
+ if (q.z)
39
+ q.z = 0;
40
+ if (q.vz)
41
+ q.vz = 0;
42
+ q.heat = 1;
43
+ // A supernova is a CONSERVATION event: the ejected matter rejoins the PERSISTENT
44
+ // field. Mortal (class-[S] source-spawned) matter that a sink captured and held is
45
+ // released immortal — so a source→sink→supernova loop visibly conserves (the matter
46
+ // the source made becomes lasting field matter, bounded by the engine's pool ceiling)
47
+ // instead of the released particles aging out and vanishing moments after release. A
48
+ // no-op for the conserved base pool (age already undefined), so the canonical sink —
49
+ // captured base particles return exactly as before — is unchanged.
50
+ q.age = undefined;
51
+ released.push(q);
52
+ }
53
+ b.accreted = 0;
54
+ return released;
55
+ }
56
+ /** Sink fill fraction ∈ [0,1] — the value written to `--load` / `--mass`. 0 when not a sink. */
57
+ export function sinkLoad(b) {
58
+ if (b.capacity <= 0)
59
+ return 0;
60
+ const f = b.accreted / b.capacity;
61
+ return f < 0 ? 0 : f > 1 ? 1 : f;
62
+ }
63
+ /**
64
+ * Capture/release event edge for a sink body. `prevArmed` is whether `captured` has fired since the
65
+ * last release; `accreting` is `b.accreted > 0` after this frame's force pass. Rising edge →
66
+ * `captured`; falling edge (which only happens via release/supernova, since load drops to 0 there)
67
+ * → `released`. Pure: the caller persists `armed` and performs the dispatch.
68
+ */
69
+ export function captureEdge(prevArmed, accreting) {
70
+ if (accreting && !prevArmed)
71
+ return { fire: 'captured', armed: true };
72
+ if (!accreting && prevArmed)
73
+ return { fire: 'released', armed: false };
74
+ return { fire: null, armed: prevArmed };
75
+ }
76
+ /**
77
+ * Attention-gated discharge (#365, the Contour Charge behavior): a sink gated on engagement
78
+ * (`data-when="active"`) releases what it holds on the FALLING edge of engagement — the vessel
79
+ * charges while attended and discharges when attention leaves. The condition pass already gates
80
+ * capture (a closed gate pulls nothing new in); this is the matching release side. Pure trigger:
81
+ * the caller supplies the release ritual (the engine passes `env.supernova`, so discharge is the
82
+ * same conserved release — same radial burst, same `field:released` event — as saturation).
83
+ * Returns the bodies that discharged this pass.
84
+ */
85
+ export function dischargeDisengaged(bodies, release) {
86
+ const discharged = [];
87
+ for (const b of bodies) {
88
+ if (b.when !== 'active' || !b.tokens.includes('sink'))
89
+ continue;
90
+ if (b.wasOn && !b.on && b.accreted > 0) {
91
+ release(b);
92
+ discharged.push(b);
93
+ }
94
+ b.wasOn = b.on;
95
+ }
96
+ return discharged;
97
+ }
98
+ //# sourceMappingURL=accretion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accretion.js","sourceRoot":"","sources":["../../src/core/accretion.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Element agents (§22.4) — a force can move a DOM element by a transform offset,
3
+ * with an anchor spring pulling it back to its layout slot. Pure integration
4
+ * here; the per-frame force probe and the transform writes live in the field loop.
5
+ */
6
+ import type { Vec2 } from './types.ts';
7
+ export interface ElementOffset {
8
+ x: number;
9
+ y: number;
10
+ vx: number;
11
+ vy: number;
12
+ }
13
+ /** Integrate an element's offset under a net force (element mass + damping, §22.4). */
14
+ export declare function integrateOffset(o: ElementOffset, fx: number, fy: number, mEl: number, friction?: number, maxOffset?: number): void;
15
+ /** The anchor-spring force pulling an offset back toward home (o = 0, §22.4). */
16
+ export declare function anchorForce(o: ElementOffset, k?: number): Vec2;
17
+ /** Element inertial mass from its rendered area (heavier = harder to move). */
18
+ export declare function elementMass(area: number): number;
19
+ /**
20
+ * Self-laying-out repulsion (Concept 3): every other element pushes this one away,
21
+ * `Σ C·(c − cⱼ)/|c − cⱼ|²` — so a cluster spreads out. Softened near coincidence so
22
+ * fully-overlapping elements don't blow up. `others` are the other element centres.
23
+ */
24
+ export declare function repelForce(self: Vec2, others: readonly Vec2[], C?: number, soft?: number): Vec2;
25
+ /**
26
+ * Density-pressure force (Concept 3): push an element *down* the local density gradient
27
+ * (toward emptier field), `−∇ρ`, estimated by a 4-tap finite difference of a density
28
+ * sampler at `±delta`. So elements drift off crowded matter toward open space.
29
+ */
30
+ export declare function densityPush(sample: (x: number, y: number) => number, x: number, y: number, delta?: number, scale?: number): Vec2;
31
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/core/agents.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,aAAa;IAC5B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,uFAAuF;AACvF,wBAAgB,eAAe,CAC7B,CAAC,EAAE,aAAa,EAChB,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,QAAQ,SAAM,EACd,SAAS,SAAK,GACb,IAAI,CAWN;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,SAAO,GAAG,IAAI,CAE5D;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,IAAI,EAAE,EAAE,CAAC,SAAO,EAAE,IAAI,SAAK,GAAG,IAAI,CAYzF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,EACxC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,KAAK,SAAK,EACV,KAAK,SAAI,GACR,IAAI,CAIN"}
@@ -0,0 +1,51 @@
1
+ /** Integrate an element's offset under a net force (element mass + damping, §22.4). */
2
+ export function integrateOffset(o, fx, fy, mEl, friction = 0.9, maxOffset = 80) {
3
+ const m = mEl > 0 ? mEl : 1;
4
+ o.vx = (o.vx + fx / m) * friction;
5
+ o.vy = (o.vy + fy / m) * friction;
6
+ o.x += o.vx;
7
+ o.y += o.vy;
8
+ const d = Math.hypot(o.x, o.y);
9
+ if (d > maxOffset) {
10
+ o.x = (o.x / d) * maxOffset;
11
+ o.y = (o.y / d) * maxOffset;
12
+ }
13
+ }
14
+ /** The anchor-spring force pulling an offset back toward home (o = 0, §22.4). */
15
+ export function anchorForce(o, k = 0.02) {
16
+ return { x: -o.x * k, y: -o.y * k };
17
+ }
18
+ /** Element inertial mass from its rendered area (heavier = harder to move). */
19
+ export function elementMass(area) {
20
+ const m = area / 30000;
21
+ return m < 0.6 ? 0.6 : m > 6 ? 6 : m;
22
+ }
23
+ /**
24
+ * Self-laying-out repulsion (Concept 3): every other element pushes this one away,
25
+ * `Σ C·(c − cⱼ)/|c − cⱼ|²` — so a cluster spreads out. Softened near coincidence so
26
+ * fully-overlapping elements don't blow up. `others` are the other element centres.
27
+ */
28
+ export function repelForce(self, others, C = 1600, soft = 26) {
29
+ let fx = 0;
30
+ let fy = 0;
31
+ const s2 = soft * soft;
32
+ for (const o of others) {
33
+ const dx = self.x - o.x;
34
+ const dy = self.y - o.y;
35
+ const d2 = dx * dx + dy * dy + s2; // softened |c − cⱼ|²
36
+ fx += (C * dx) / d2; // magnitude ≈ C/|d| along the separation unit vector
37
+ fy += (C * dy) / d2;
38
+ }
39
+ return { x: fx, y: fy };
40
+ }
41
+ /**
42
+ * Density-pressure force (Concept 3): push an element *down* the local density gradient
43
+ * (toward emptier field), `−∇ρ`, estimated by a 4-tap finite difference of a density
44
+ * sampler at `±delta`. So elements drift off crowded matter toward open space.
45
+ */
46
+ export function densityPush(sample, x, y, delta = 16, scale = 1) {
47
+ const gx = (sample(x + delta, y) - sample(x - delta, y)) / (2 * delta);
48
+ const gy = (sample(x, y + delta) - sample(x, y - delta)) / (2 * delta);
49
+ return { x: -gx * scale, y: -gy * scale }; // negative gradient → toward lower density
50
+ }
51
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents.js","sourceRoot":"","sources":["../../src/core/agents.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Conserved attention (§2.4 + possibilities Concept 2) — one finite *strength
3
+ * budget* for the whole page. Engaging a body raises its demand and, because the
4
+ * total is conserved, pulls allocation off every other body: the field physically
5
+ * cannot emphasise two things at once. Navigation becomes moving force between
6
+ * sections, not fading opacity.
7
+ *
8
+ * This is pure: it returns a per-body effective-strength multiplier the integrator
9
+ * folds into each body's force. The model is feed-forward on *demand* (configured
10
+ * strength × engagement), which gives it two properties worth keeping:
11
+ *
12
+ * 1. Rest-neutral — when nothing is engaged every multiplier is exactly 1, so a
13
+ * field that opts in is unchanged until something is actually engaged.
14
+ * 2. Total-strength-conserving — Σ Sᵢ·mulᵢ = Σ Sᵢ (within the clamp). The budget
15
+ * is literally invariant frame to frame; a boost to one body is exactly the
16
+ * starvation of the others.
17
+ *
18
+ * demandᵢ = 1 + β·onᵢ (β = engagement multiplier)
19
+ * mulᵢ = demandᵢ · (Σ Sⱼ / Σ Sⱼ·demandⱼ)
20
+ *
21
+ * The closed form drops out: the normaliser `k = ΣS / ΣM` is 1 when nothing is
22
+ * engaged, < 1 once anything is, so idle bodies dim (·k) while engaged bodies gain
23
+ * ((1+β)·k). A density-closed-loop variant (steer toward actual fill, §8) is a
24
+ * possible refinement; this feed-forward form is chosen first for stability.
25
+ */
26
+ export interface AttnInput {
27
+ /** the body's configured force magnitude S. */
28
+ strength: number;
29
+ /** engaged (hover / focus / tap). */
30
+ on: boolean;
31
+ }
32
+ export interface AttnOpts {
33
+ /** engagement multiplier β — how much harder an engaged body competes (default 2). */
34
+ beta?: number;
35
+ /** clamp floor for the multiplier (default 0.25). */
36
+ lo?: number;
37
+ /** clamp ceiling for the multiplier (default 3). */
38
+ hi?: number;
39
+ }
40
+ export interface AttnAllocItem {
41
+ /** the item's competitive demand — any non-negative magnitude (a lens-weighted urgency, say). */
42
+ urgency: number;
43
+ /** pinned items sit outside the competition: each takes exactly `cap` off the top. */
44
+ pinned?: boolean;
45
+ }
46
+ export interface AttnAllocOpts {
47
+ /** per-item weight ceiling (default 1). */
48
+ cap?: number;
49
+ }
50
+ /**
51
+ * Conserved allocation (§2.4 — one finite budget): distribute `budget` across items
52
+ * proportional to `urgency`, capping each weight at `cap` (default 1) and re-flowing
53
+ * capped excess over the rest (water-filling). `pinned` items take exactly `cap` off
54
+ * the top; the remaining budget water-fills over the unpinned by urgency.
55
+ *
56
+ * Invariant: Σ(returned) === budget (±ε) whenever budget ≤ items.length × cap and the
57
+ * unpinned items carry any positive urgency. Zero/negative/non-finite urgencies get 0 —
58
+ * the budget only flows where there is demand, so an all-zero unpinned set allocates
59
+ * nothing (extracted as-is from the Inbox example, where this never starves anyone:
60
+ * urgencies are blends of normalized signals). Past the ceiling (budget > N × cap)
61
+ * every weight saturates at `cap`. Pure; deterministic; never NaN; never negative;
62
+ * each weight ≤ cap. Each water-filling pass either finishes or caps at least one
63
+ * item, so N passes always converge.
64
+ */
65
+ export declare function allocateAttention(items: ReadonlyArray<AttnAllocItem>, budget: number, opts?: AttnAllocOpts): number[];
66
+ /**
67
+ * The per-body effective-strength multipliers for one frame, index-aligned with
68
+ * `bodies`. All 1 when nothing is engaged or the input is degenerate (empty /
69
+ * non-positive total), so it is always safe to apply.
70
+ */
71
+ export declare function attentionMuls(bodies: readonly AttnInput[], opts?: AttnOpts): number[];
72
+ //# sourceMappingURL=attention.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.d.ts","sourceRoot":"","sources":["../../src/core/attention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,SAAS;IACxB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,QAAQ;IACvB,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qDAAqD;IACrD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,oDAAoD;IACpD,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,aAAa,CAAC,aAAa,CAAC,EACnC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,aAAkB,GACvB,MAAM,EAAE,CA2CV;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,GAAE,QAAa,GAAG,MAAM,EAAE,CA0BzF"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Conserved attention (§2.4 + possibilities Concept 2) — one finite *strength
3
+ * budget* for the whole page. Engaging a body raises its demand and, because the
4
+ * total is conserved, pulls allocation off every other body: the field physically
5
+ * cannot emphasise two things at once. Navigation becomes moving force between
6
+ * sections, not fading opacity.
7
+ *
8
+ * This is pure: it returns a per-body effective-strength multiplier the integrator
9
+ * folds into each body's force. The model is feed-forward on *demand* (configured
10
+ * strength × engagement), which gives it two properties worth keeping:
11
+ *
12
+ * 1. Rest-neutral — when nothing is engaged every multiplier is exactly 1, so a
13
+ * field that opts in is unchanged until something is actually engaged.
14
+ * 2. Total-strength-conserving — Σ Sᵢ·mulᵢ = Σ Sᵢ (within the clamp). The budget
15
+ * is literally invariant frame to frame; a boost to one body is exactly the
16
+ * starvation of the others.
17
+ *
18
+ * demandᵢ = 1 + β·onᵢ (β = engagement multiplier)
19
+ * mulᵢ = demandᵢ · (Σ Sⱼ / Σ Sⱼ·demandⱼ)
20
+ *
21
+ * The closed form drops out: the normaliser `k = ΣS / ΣM` is 1 when nothing is
22
+ * engaged, < 1 once anything is, so idle bodies dim (·k) while engaged bodies gain
23
+ * ((1+β)·k). A density-closed-loop variant (steer toward actual fill, §8) is a
24
+ * possible refinement; this feed-forward form is chosen first for stability.
25
+ */
26
+ /**
27
+ * Conserved allocation (§2.4 — one finite budget): distribute `budget` across items
28
+ * proportional to `urgency`, capping each weight at `cap` (default 1) and re-flowing
29
+ * capped excess over the rest (water-filling). `pinned` items take exactly `cap` off
30
+ * the top; the remaining budget water-fills over the unpinned by urgency.
31
+ *
32
+ * Invariant: Σ(returned) === budget (±ε) whenever budget ≤ items.length × cap and the
33
+ * unpinned items carry any positive urgency. Zero/negative/non-finite urgencies get 0 —
34
+ * the budget only flows where there is demand, so an all-zero unpinned set allocates
35
+ * nothing (extracted as-is from the Inbox example, where this never starves anyone:
36
+ * urgencies are blends of normalized signals). Past the ceiling (budget > N × cap)
37
+ * every weight saturates at `cap`. Pure; deterministic; never NaN; never negative;
38
+ * each weight ≤ cap. Each water-filling pass either finishes or caps at least one
39
+ * item, so N passes always converge.
40
+ */
41
+ export function allocateAttention(items, budget, opts = {}) {
42
+ const cap = opts.cap ?? 1;
43
+ const n = items.length;
44
+ const w = new Array(n).fill(0);
45
+ if (n === 0 || !(cap > 0))
46
+ return w;
47
+ // pins first — each holds exactly `cap`, off the top of the budget.
48
+ const u = new Array(n).fill(0);
49
+ let free = [];
50
+ let pinnedCount = 0;
51
+ for (let i = 0; i < n; i++) {
52
+ const it = items[i];
53
+ if (it.pinned) {
54
+ w[i] = cap;
55
+ pinnedCount++;
56
+ }
57
+ else {
58
+ u[i] = Number.isFinite(it.urgency) && it.urgency > 0 ? it.urgency : 0;
59
+ free.push(i);
60
+ }
61
+ }
62
+ // water-fill the rest: scale urgencies so the round sums to the remaining budget,
63
+ // saturate anything that would exceed `cap`, re-flow the freed budget over the rest.
64
+ let rem = Math.max(0, budget - pinnedCount * cap);
65
+ for (let pass = 0; pass < n && free.length && rem > 0; pass++) {
66
+ const sum = free.reduce((s, i) => s + u[i], 0) || 1;
67
+ const k = rem / sum;
68
+ const still = [];
69
+ let capped = 0;
70
+ for (const i of free) {
71
+ if (u[i] * k >= cap) {
72
+ w[i] = cap;
73
+ capped++;
74
+ }
75
+ else
76
+ still.push(i);
77
+ }
78
+ if (!capped) {
79
+ for (const i of still)
80
+ w[i] = u[i] * k;
81
+ break;
82
+ }
83
+ rem -= capped * cap; // provably ≥ 0: each capped share was ≥ cap of a round summing to rem
84
+ free = still;
85
+ }
86
+ return w;
87
+ }
88
+ /**
89
+ * The per-body effective-strength multipliers for one frame, index-aligned with
90
+ * `bodies`. All 1 when nothing is engaged or the input is degenerate (empty /
91
+ * non-positive total), so it is always safe to apply.
92
+ */
93
+ export function attentionMuls(bodies, opts = {}) {
94
+ const beta = opts.beta ?? 2;
95
+ const lo = opts.lo ?? 0.25;
96
+ const hi = opts.hi ?? 3;
97
+ const n = bodies.length;
98
+ const out = new Array(n).fill(1);
99
+ if (n === 0)
100
+ return out;
101
+ let sumS = 0;
102
+ let sumM = 0;
103
+ for (const b of bodies) {
104
+ const s = b.strength > 0 ? b.strength : 0;
105
+ sumS += s;
106
+ sumM += s * (1 + (b.on ? beta : 0));
107
+ }
108
+ if (sumS <= 0 || sumM <= 0)
109
+ return out; // nothing to allocate → leave neutral
110
+ const k = sumS / sumM; // demand normaliser; exactly 1 when nothing is engaged
111
+ for (let i = 0; i < n; i++) {
112
+ const demand = 1 + (bodies[i].on ? beta : 0);
113
+ let mul = demand * k;
114
+ if (mul < lo)
115
+ mul = lo;
116
+ else if (mul > hi)
117
+ mul = hi;
118
+ out[i] = mul;
119
+ }
120
+ return out;
121
+ }
122
+ //# sourceMappingURL=attention.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention.js","sourceRoot":"","sources":["../../src/core/attention.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Cross-boundary causality (possibilities Concept 4) — density doesn't stop at a
3
+ * body's edge. When a body saturates (its eased density `d` climbs past a
4
+ * threshold, e.g. because it's engaged and gathering matter), the excess **spills
5
+ * to its neighbours**, weighted by proximity, as a conserved transfer. Hover one
6
+ * card and the ones beside it light up; the wiring between elements appears because
7
+ * matter actually flows between them, not because it was hand-drawn (§10 threads,
8
+ * made emergent).
9
+ *
10
+ * Pure: given each body's density and centre, returns the per-body **lit delta** —
11
+ * how much density it receives from (or donates to) its neighbours this frame. The
12
+ * caller adds it to `d` to get the body's `lit` signal, writes that to the element,
13
+ * and fires a DOM event on threshold crossing.
14
+ *
15
+ * excessᵢ = max(0, dᵢ − θ)
16
+ * wᵢⱼ = max(0, 1 − dist(i,j)/falloff) proximity weight, bounded (no 1/d blow-up)
17
+ * Φᵢⱼ = κ · excessᵢ · wᵢⱼ / Σₖ wᵢₖ i donates, j receives
18
+ * Δⱼ = Σᵢ Φᵢⱼ − Σⱼ Φⱼₖ received − donated
19
+ *
20
+ * Conserved by construction: ΣΔ = 0 (every donation is exactly a reception).
21
+ */
22
+ export interface SpillBody {
23
+ /** eased density d ∈ [0,1] (§8). */
24
+ d: number;
25
+ cx: number;
26
+ cy: number;
27
+ }
28
+ export interface SpillOpts {
29
+ /** density above which a body spills its excess (default 0.55). */
30
+ threshold?: number;
31
+ /** fraction of the excess that flows out (default 0.6). */
32
+ kappa?: number;
33
+ /** proximity reach in px — past this, no transfer (default 320). */
34
+ falloff?: number;
35
+ }
36
+ /** Per-body lit delta (received − donated), index-aligned with `bodies`. Sums to 0. */
37
+ export declare function spillover(bodies: readonly SpillBody[], opts?: SpillOpts): number[];
38
+ //# sourceMappingURL=causality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"causality.d.ts","sourceRoot":"","sources":["../../src/core/causality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,SAAS;IACxB,oCAAoC;IACpC,CAAC,EAAE,MAAM,CAAC;IACV,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,SAAS;IACxB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,GAAE,SAAc,GAAG,MAAM,EAAE,CAuCtF"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Cross-boundary causality (possibilities Concept 4) — density doesn't stop at a
3
+ * body's edge. When a body saturates (its eased density `d` climbs past a
4
+ * threshold, e.g. because it's engaged and gathering matter), the excess **spills
5
+ * to its neighbours**, weighted by proximity, as a conserved transfer. Hover one
6
+ * card and the ones beside it light up; the wiring between elements appears because
7
+ * matter actually flows between them, not because it was hand-drawn (§10 threads,
8
+ * made emergent).
9
+ *
10
+ * Pure: given each body's density and centre, returns the per-body **lit delta** —
11
+ * how much density it receives from (or donates to) its neighbours this frame. The
12
+ * caller adds it to `d` to get the body's `lit` signal, writes that to the element,
13
+ * and fires a DOM event on threshold crossing.
14
+ *
15
+ * excessᵢ = max(0, dᵢ − θ)
16
+ * wᵢⱼ = max(0, 1 − dist(i,j)/falloff) proximity weight, bounded (no 1/d blow-up)
17
+ * Φᵢⱼ = κ · excessᵢ · wᵢⱼ / Σₖ wᵢₖ i donates, j receives
18
+ * Δⱼ = Σᵢ Φᵢⱼ − Σⱼ Φⱼₖ received − donated
19
+ *
20
+ * Conserved by construction: ΣΔ = 0 (every donation is exactly a reception).
21
+ */
22
+ /** Per-body lit delta (received − donated), index-aligned with `bodies`. Sums to 0. */
23
+ export function spillover(bodies, opts = {}) {
24
+ const threshold = opts.threshold ?? 0.55;
25
+ const kappa = opts.kappa ?? 0.6;
26
+ const falloff = opts.falloff ?? 320;
27
+ const n = bodies.length;
28
+ const delta = new Array(n).fill(0);
29
+ if (n < 2)
30
+ return delta;
31
+ const w = new Array(n).fill(0);
32
+ for (let i = 0; i < n; i++) {
33
+ const bi = bodies[i];
34
+ const excess = bi.d - threshold;
35
+ if (excess <= 0)
36
+ continue;
37
+ // proximity weights to every other body within reach
38
+ let total = 0;
39
+ for (let j = 0; j < n; j++) {
40
+ if (j === i) {
41
+ w[j] = 0;
42
+ continue;
43
+ }
44
+ const bj = bodies[j];
45
+ const dist = Math.hypot(bi.cx - bj.cx, bi.cy - bj.cy);
46
+ const ww = dist < falloff ? 1 - dist / falloff : 0;
47
+ w[j] = ww;
48
+ total += ww;
49
+ }
50
+ if (total <= 0)
51
+ continue;
52
+ const out = kappa * excess; // total density this body spills
53
+ for (let j = 0; j < n; j++) {
54
+ const wj = w[j];
55
+ if (wj <= 0)
56
+ continue;
57
+ const phi = (out * wj) / total;
58
+ delta[j] += phi; // neighbour receives
59
+ delta[i] -= phi; // this body donates (conserved)
60
+ }
61
+ }
62
+ return delta;
63
+ }
64
+ //# sourceMappingURL=causality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"causality.js","sourceRoot":"","sources":["../../src/core/causality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAkBH,uFAAuF;AACvF,MAAM,UAAU,SAAS,CAAC,MAA4B,EAAE,OAAkB,EAAE;IAC1E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;IACpC,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAExB,MAAM,CAAC,GAAG,IAAI,KAAK,CAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACtB,MAAM,MAAM,GAAG,EAAE,CAAC,CAAC,GAAG,SAAS,CAAC;QAChC,IAAI,MAAM,IAAI,CAAC;YAAE,SAAS;QAE1B,qDAAqD;QACrD,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACZ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBACT,SAAS;YACX,CAAC;YACD,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;YACtB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YACtD,MAAM,EAAE,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;YACnD,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YACV,KAAK,IAAI,EAAE,CAAC;QACd,CAAC;QACD,IAAI,KAAK,IAAI,CAAC;YAAE,SAAS;QAEzB,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,CAAC,iCAAiC;QAC7D,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;YACjB,IAAI,EAAE,IAAI,CAAC;gBAAE,SAAS;YACtB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC;YAC/B,KAAK,CAAC,CAAC,CAAE,IAAI,GAAG,CAAC,CAAC,qBAAqB;YACvC,KAAK,CAAC,CAAC,CAAE,IAAI,GAAG,CAAC,CAAC,gCAAgC;QACpD,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Built-in `data-when` gate predicates (§5). Selective gates read each particle;
3
+ * `active` reads the body; `scrolling` reads the shared frame state (`env.scrollV`),
4
+ * so it acts only while the page is actually scrolling.
5
+ */
6
+ import type { Body, ConditionRegistry, Env, Particle } from './types.ts';
7
+ export declare const conditions: ConditionRegistry;
8
+ /** Does body `b`'s gate pass for particle `p`? Empty gate (`''`) always passes. */
9
+ export declare function passes(reg: ConditionRegistry, b: Body, p: Particle, env?: Env): boolean;
10
+ //# sourceMappingURL=conditions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../../src/core/conditions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAa,iBAAiB,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEpF,eAAO,MAAM,UAAU,EAAE,iBAQxB,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,MAAM,CAAC,GAAG,EAAE,iBAAiB,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAC,EAAE,GAAG,GAAG,OAAO,CAIvF"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Built-in `data-when` gate predicates (§5). Selective gates read each particle;
3
+ * `active` reads the body; `scrolling` reads the shared frame state (`env.scrollV`),
4
+ * so it acts only while the page is actually scrolling.
5
+ */
6
+ export const conditions = {
7
+ active: (b) => b.on,
8
+ // speed gates read the full 3D speed (z-axis.md) — vz is 0 in a flat field.
9
+ fast: (_b, p) => p.vx * p.vx + p.vy * p.vy + (p.vz ?? 0) * (p.vz ?? 0) > 0.9,
10
+ slow: (_b, p) => p.vx * p.vx + p.vy * p.vy + (p.vz ?? 0) * (p.vz ?? 0) < 0.22,
11
+ hot: (_b, p) => p.heat > 0.3,
12
+ cool: (_b, p) => p.heat < 0.08,
13
+ scrolling: (_b, _p, env) => (env?.scrollV ?? 0) > 0.25,
14
+ };
15
+ /** Does body `b`'s gate pass for particle `p`? Empty gate (`''`) always passes. */
16
+ export function passes(reg, b, p, env) {
17
+ if (!b.when)
18
+ return true;
19
+ const fn = reg[b.when];
20
+ return fn ? fn(b, p, env) : true;
21
+ }
22
+ //# sourceMappingURL=conditions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conditions.js","sourceRoot":"","sources":["../../src/core/conditions.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,CAAC,MAAM,UAAU,GAAsB;IAC3C,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE;IACnB,4EAA4E;IAC5E,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,GAAG;IAC5E,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,IAAI;IAC7E,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG;IAC5B,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,IAAI;IAC9B,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,IAAI,CAAC,CAAC,GAAG,IAAI;CACvD,CAAC;AAEF,mFAAmF;AACnF,MAAM,UAAU,MAAM,CAAC,GAAsB,EAAE,CAAO,EAAE,CAAW,EAAE,GAAS;IAC5E,IAAI,CAAC,CAAC,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,EAAE,GAA0B,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC9C,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACnC,CAAC"}