@fundamental-engine/core 0.9.2 → 0.9.4

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 (355) hide show
  1. package/README.md +12 -11
  2. package/dist/agents/event-agent.d.ts.map +1 -1
  3. package/dist/agents/event-agent.js +13 -9
  4. package/dist/agents/event-agent.js.map +1 -1
  5. package/dist/config/forces.config.d.ts +1 -1
  6. package/dist/config/forces.config.d.ts.map +1 -1
  7. package/dist/config/forces.config.js +2 -2
  8. package/dist/config/forces.config.js.map +1 -1
  9. package/dist/config/themes.d.ts +1 -1
  10. package/dist/conformance/run.d.ts +3 -3
  11. package/dist/conformance/run.d.ts.map +1 -1
  12. package/dist/conformance/run.js +36 -33
  13. package/dist/conformance/run.js.map +1 -1
  14. package/dist/conformance/types.d.ts +4 -1
  15. package/dist/conformance/types.d.ts.map +1 -1
  16. package/dist/contracts/guards.d.ts +4 -2
  17. package/dist/contracts/guards.d.ts.map +1 -1
  18. package/dist/contracts/guards.js.map +1 -1
  19. package/dist/contracts/passport.d.ts +1 -1
  20. package/dist/contracts/passport.d.ts.map +1 -1
  21. package/dist/contracts/passport.js +9 -0
  22. package/dist/contracts/passport.js.map +1 -1
  23. package/dist/diagnostics/energy.d.ts +1 -1
  24. package/dist/diagnostics/energy.d.ts.map +1 -1
  25. package/dist/diagnostics/fields.d.ts +1 -1
  26. package/dist/diagnostics/fields.d.ts.map +1 -1
  27. package/dist/diagnostics/modes.d.ts +1 -1
  28. package/dist/diagnostics/modes.d.ts.map +1 -1
  29. package/dist/diagnostics/modes.js +1 -1
  30. package/dist/diagnostics/modes.js.map +1 -1
  31. package/dist/diagnostics/potential.d.ts +1 -1
  32. package/dist/diagnostics/potential.d.ts.map +1 -1
  33. package/dist/diagnostics/probes.d.ts +1 -1
  34. package/dist/diagnostics/probes.d.ts.map +1 -1
  35. package/dist/diagnostics/probes.js +1 -1
  36. package/dist/diagnostics/probes.js.map +1 -1
  37. package/dist/diagnostics/render.d.ts +1 -1
  38. package/dist/diagnostics/render.d.ts.map +1 -1
  39. package/dist/{core → engine}/accretion.d.ts +1 -1
  40. package/dist/engine/accretion.d.ts.map +1 -0
  41. package/dist/{core → engine}/accretion.js +1 -1
  42. package/dist/engine/accretion.js.map +1 -0
  43. package/dist/engine/agents.d.ts.map +1 -0
  44. package/dist/engine/agents.js.map +1 -0
  45. package/dist/engine/attention.d.ts.map +1 -0
  46. package/dist/engine/attention.js.map +1 -0
  47. package/dist/engine/causality.d.ts.map +1 -0
  48. package/dist/engine/causality.js.map +1 -0
  49. package/dist/engine/conditions.d.ts.map +1 -0
  50. package/dist/engine/conditions.js.map +1 -0
  51. package/dist/{core → engine}/currents.d.ts +1 -1
  52. package/dist/engine/currents.d.ts.map +1 -0
  53. package/dist/engine/currents.js.map +1 -0
  54. package/dist/engine/dock.d.ts.map +1 -0
  55. package/dist/engine/dock.js.map +1 -0
  56. package/dist/{core → engine}/events.d.ts +7 -4
  57. package/dist/engine/events.d.ts.map +1 -0
  58. package/dist/{core → engine}/events.js +8 -1
  59. package/dist/engine/events.js.map +1 -0
  60. package/dist/{core → engine}/feedback-sink.d.ts +1 -1
  61. package/dist/engine/feedback-sink.d.ts.map +1 -0
  62. package/dist/{core → engine}/feedback-sink.js +1 -2
  63. package/dist/engine/feedback-sink.js.map +1 -0
  64. package/dist/engine/feedback.d.ts.map +1 -0
  65. package/dist/engine/feedback.js.map +1 -0
  66. package/dist/engine/field-snapshot.d.ts.map +1 -0
  67. package/dist/engine/field-snapshot.js.map +1 -0
  68. package/dist/engine/field-store.d.ts.map +1 -0
  69. package/dist/engine/field-store.js.map +1 -0
  70. package/dist/engine/field.d.ts.map +1 -0
  71. package/dist/{core → engine}/field.js +844 -83
  72. package/dist/engine/field.js.map +1 -0
  73. package/dist/engine/fieldline-seeds.d.ts.map +1 -0
  74. package/dist/engine/fieldline-seeds.js.map +1 -0
  75. package/dist/engine/fieldlines.d.ts.map +1 -0
  76. package/dist/{core → engine}/fieldlines.js +1 -1
  77. package/dist/engine/fieldlines.js.map +1 -0
  78. package/dist/engine/flow.d.ts.map +1 -0
  79. package/dist/engine/flow.js.map +1 -0
  80. package/dist/engine/formations.d.ts.map +1 -0
  81. package/dist/engine/formations.js.map +1 -0
  82. package/dist/{core → engine}/frame-harness.d.ts +2 -2
  83. package/dist/engine/frame-harness.d.ts.map +1 -0
  84. package/dist/{core → engine}/frame-harness.js +2 -2
  85. package/dist/engine/frame-harness.js.map +1 -0
  86. package/dist/engine/governance.d.ts.map +1 -0
  87. package/dist/engine/governance.js.map +1 -0
  88. package/dist/engine/heatmap.d.ts.map +1 -0
  89. package/dist/{core → engine}/heatmap.js +1 -1
  90. package/dist/engine/heatmap.js.map +1 -0
  91. package/dist/engine/host-headless.d.ts.map +1 -0
  92. package/dist/engine/host-headless.js.map +1 -0
  93. package/dist/engine/host.d.ts +127 -0
  94. package/dist/engine/host.d.ts.map +1 -0
  95. package/dist/engine/host.js +63 -0
  96. package/dist/engine/host.js.map +1 -0
  97. package/dist/{core → engine}/integrator.d.ts +22 -0
  98. package/dist/engine/integrator.d.ts.map +1 -0
  99. package/dist/{core → engine}/integrator.js +123 -18
  100. package/dist/engine/integrator.js.map +1 -0
  101. package/dist/engine/lane-registry.d.ts.map +1 -0
  102. package/dist/engine/lane-registry.js.map +1 -0
  103. package/dist/engine/projection-agent-json.d.ts.map +1 -0
  104. package/dist/engine/projection-agent-json.js.map +1 -0
  105. package/dist/engine/query-lens.d.ts.map +1 -0
  106. package/dist/engine/query-lens.js.map +1 -0
  107. package/dist/{core → engine}/reactions.d.ts +7 -0
  108. package/dist/engine/reactions.d.ts.map +1 -0
  109. package/dist/{core → engine}/reactions.js +7 -0
  110. package/dist/engine/reactions.js.map +1 -0
  111. package/dist/engine/registry.d.ts.map +1 -0
  112. package/dist/engine/registry.js.map +1 -0
  113. package/dist/engine/render-backend.d.ts.map +1 -0
  114. package/dist/engine/render-backend.js.map +1 -0
  115. package/dist/engine/render-modes.d.ts +106 -0
  116. package/dist/engine/render-modes.d.ts.map +1 -0
  117. package/dist/engine/render-modes.js +306 -0
  118. package/dist/engine/render-modes.js.map +1 -0
  119. package/dist/engine/reservoir.d.ts.map +1 -0
  120. package/dist/engine/reservoir.js.map +1 -0
  121. package/dist/engine/scalar-grid.d.ts.map +1 -0
  122. package/dist/engine/scalar-grid.js.map +1 -0
  123. package/dist/{core → engine}/scanner.d.ts +29 -3
  124. package/dist/engine/scanner.d.ts.map +1 -0
  125. package/dist/{core → engine}/scanner.js +41 -3
  126. package/dist/engine/scanner.js.map +1 -0
  127. package/dist/engine/shadow.d.ts.map +1 -0
  128. package/dist/engine/shadow.js.map +1 -0
  129. package/dist/{core → engine}/spatial-hash.d.ts +8 -0
  130. package/dist/engine/spatial-hash.d.ts.map +1 -0
  131. package/dist/{core → engine}/spatial-hash.js +26 -3
  132. package/dist/engine/spatial-hash.js.map +1 -0
  133. package/dist/engine/streamlines.d.ts.map +1 -0
  134. package/dist/engine/streamlines.js.map +1 -0
  135. package/dist/engine/surface.d.ts.map +1 -0
  136. package/dist/engine/surface.js.map +1 -0
  137. package/dist/engine/temporal.d.ts.map +1 -0
  138. package/dist/engine/temporal.js.map +1 -0
  139. package/dist/engine/thermo.d.ts.map +1 -0
  140. package/dist/engine/thermo.js.map +1 -0
  141. package/dist/{core → engine}/types.d.ts +413 -16
  142. package/dist/engine/types.d.ts.map +1 -0
  143. package/dist/engine/types.js.map +1 -0
  144. package/dist/engine/weights.d.ts.map +1 -0
  145. package/dist/engine/weights.js.map +1 -0
  146. package/dist/forces/extended.d.ts +2 -2
  147. package/dist/forces/extended.d.ts.map +1 -1
  148. package/dist/forces/extended.js +9 -3
  149. package/dist/forces/extended.js.map +1 -1
  150. package/dist/forces/index.d.ts +2 -2
  151. package/dist/forces/index.d.ts.map +1 -1
  152. package/dist/forces/natural.d.ts +2 -2
  153. package/dist/forces/natural.d.ts.map +1 -1
  154. package/dist/forces/natural.js +11 -12
  155. package/dist/forces/natural.js.map +1 -1
  156. package/dist/index.d.ts +40 -40
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +40 -40
  159. package/dist/index.js.map +1 -1
  160. package/dist/{core → math}/geometry.d.ts +10 -1
  161. package/dist/math/geometry.d.ts.map +1 -0
  162. package/dist/{core → math}/geometry.js +16 -0
  163. package/dist/{core → math}/geometry.js.map +1 -1
  164. package/dist/{core → math}/math.d.ts.map +1 -1
  165. package/dist/{core → math}/math.js.map +1 -1
  166. package/dist/recipes/catalog.d.ts +6 -2
  167. package/dist/recipes/catalog.d.ts.map +1 -1
  168. package/dist/recipes/catalog.js +9 -5
  169. package/dist/recipes/catalog.js.map +1 -1
  170. package/dist/recipes/compile.d.ts +7 -3
  171. package/dist/recipes/compile.d.ts.map +1 -1
  172. package/dist/recipes/compile.js +6 -2
  173. package/dist/recipes/compile.js.map +1 -1
  174. package/dist/recipes/focus.d.ts +18 -0
  175. package/dist/recipes/focus.d.ts.map +1 -0
  176. package/dist/recipes/focus.js +34 -0
  177. package/dist/recipes/focus.js.map +1 -0
  178. package/dist/recipes/index.d.ts +1 -0
  179. package/dist/recipes/index.d.ts.map +1 -1
  180. package/dist/recipes/index.js +1 -0
  181. package/dist/recipes/index.js.map +1 -1
  182. package/dist/recipes/schema.d.ts +10 -5
  183. package/dist/recipes/schema.d.ts.map +1 -1
  184. package/dist/recipes/schema.js +2 -1
  185. package/dist/recipes/schema.js.map +1 -1
  186. package/dist/recipes/wayfinding.d.ts +4 -2
  187. package/dist/recipes/wayfinding.d.ts.map +1 -1
  188. package/dist/recipes/wayfinding.js +6 -2
  189. package/dist/recipes/wayfinding.js.map +1 -1
  190. package/dist/record/record.d.ts +2 -2
  191. package/dist/record/record.d.ts.map +1 -1
  192. package/dist/record/record.js +3 -3
  193. package/dist/record/record.js.map +1 -1
  194. package/dist/semantic/layers.d.ts +16 -2
  195. package/dist/semantic/layers.d.ts.map +1 -1
  196. package/dist/semantic/layers.js +20 -3
  197. package/dist/semantic/layers.js.map +1 -1
  198. package/dist/semantic/materials.js +3 -3
  199. package/dist/semantic/materials.js.map +1 -1
  200. package/dist/semantic/states.js +1 -1
  201. package/dist/semantic/states.js.map +1 -1
  202. package/dist/version.d.ts +1 -1
  203. package/dist/version.js +1 -1
  204. package/dist/visual/channels.js +1 -1
  205. package/dist/visual/mapping.js +1 -1
  206. package/dist/visual/visualization.d.ts.map +1 -1
  207. package/dist/visual/visualization.js +4 -0
  208. package/dist/visual/visualization.js.map +1 -1
  209. package/package.json +2 -1
  210. package/dist/core/accretion.d.ts.map +0 -1
  211. package/dist/core/accretion.js.map +0 -1
  212. package/dist/core/agents.d.ts.map +0 -1
  213. package/dist/core/agents.js.map +0 -1
  214. package/dist/core/attention.d.ts.map +0 -1
  215. package/dist/core/attention.js.map +0 -1
  216. package/dist/core/causality.d.ts.map +0 -1
  217. package/dist/core/causality.js.map +0 -1
  218. package/dist/core/conditions.d.ts.map +0 -1
  219. package/dist/core/conditions.js.map +0 -1
  220. package/dist/core/currents.d.ts.map +0 -1
  221. package/dist/core/currents.js.map +0 -1
  222. package/dist/core/dock.d.ts.map +0 -1
  223. package/dist/core/dock.js.map +0 -1
  224. package/dist/core/events.d.ts.map +0 -1
  225. package/dist/core/events.js.map +0 -1
  226. package/dist/core/feedback-sink.d.ts.map +0 -1
  227. package/dist/core/feedback-sink.js.map +0 -1
  228. package/dist/core/feedback.d.ts.map +0 -1
  229. package/dist/core/feedback.js.map +0 -1
  230. package/dist/core/field-snapshot.d.ts.map +0 -1
  231. package/dist/core/field-snapshot.js.map +0 -1
  232. package/dist/core/field-store.d.ts.map +0 -1
  233. package/dist/core/field-store.js.map +0 -1
  234. package/dist/core/field.d.ts.map +0 -1
  235. package/dist/core/field.js.map +0 -1
  236. package/dist/core/fieldline-seeds.d.ts.map +0 -1
  237. package/dist/core/fieldline-seeds.js.map +0 -1
  238. package/dist/core/fieldlines.d.ts.map +0 -1
  239. package/dist/core/fieldlines.js.map +0 -1
  240. package/dist/core/flow.d.ts.map +0 -1
  241. package/dist/core/flow.js.map +0 -1
  242. package/dist/core/formations.d.ts.map +0 -1
  243. package/dist/core/formations.js.map +0 -1
  244. package/dist/core/frame-harness.d.ts.map +0 -1
  245. package/dist/core/frame-harness.js.map +0 -1
  246. package/dist/core/geometry.d.ts.map +0 -1
  247. package/dist/core/governance.d.ts.map +0 -1
  248. package/dist/core/governance.js.map +0 -1
  249. package/dist/core/heatmap.d.ts.map +0 -1
  250. package/dist/core/heatmap.js.map +0 -1
  251. package/dist/core/host-headless.d.ts.map +0 -1
  252. package/dist/core/host-headless.js.map +0 -1
  253. package/dist/core/host.d.ts +0 -53
  254. package/dist/core/host.d.ts.map +0 -1
  255. package/dist/core/host.js +0 -11
  256. package/dist/core/host.js.map +0 -1
  257. package/dist/core/integrator.d.ts.map +0 -1
  258. package/dist/core/integrator.js.map +0 -1
  259. package/dist/core/lane-registry.d.ts.map +0 -1
  260. package/dist/core/lane-registry.js.map +0 -1
  261. package/dist/core/projection-agent-json.d.ts.map +0 -1
  262. package/dist/core/projection-agent-json.js.map +0 -1
  263. package/dist/core/query-lens.d.ts.map +0 -1
  264. package/dist/core/query-lens.js.map +0 -1
  265. package/dist/core/reactions.d.ts.map +0 -1
  266. package/dist/core/reactions.js.map +0 -1
  267. package/dist/core/registry.d.ts.map +0 -1
  268. package/dist/core/registry.js.map +0 -1
  269. package/dist/core/render-backend.d.ts.map +0 -1
  270. package/dist/core/render-backend.js.map +0 -1
  271. package/dist/core/render-modes.d.ts +0 -42
  272. package/dist/core/render-modes.d.ts.map +0 -1
  273. package/dist/core/render-modes.js +0 -141
  274. package/dist/core/render-modes.js.map +0 -1
  275. package/dist/core/reservoir.d.ts.map +0 -1
  276. package/dist/core/reservoir.js.map +0 -1
  277. package/dist/core/scalar-grid.d.ts.map +0 -1
  278. package/dist/core/scalar-grid.js.map +0 -1
  279. package/dist/core/scanner.d.ts.map +0 -1
  280. package/dist/core/scanner.js.map +0 -1
  281. package/dist/core/shadow.d.ts.map +0 -1
  282. package/dist/core/shadow.js.map +0 -1
  283. package/dist/core/spatial-hash.d.ts.map +0 -1
  284. package/dist/core/spatial-hash.js.map +0 -1
  285. package/dist/core/streamlines.d.ts.map +0 -1
  286. package/dist/core/streamlines.js.map +0 -1
  287. package/dist/core/surface.d.ts.map +0 -1
  288. package/dist/core/surface.js.map +0 -1
  289. package/dist/core/temporal.d.ts.map +0 -1
  290. package/dist/core/temporal.js.map +0 -1
  291. package/dist/core/thermo.d.ts.map +0 -1
  292. package/dist/core/thermo.js.map +0 -1
  293. package/dist/core/types.d.ts.map +0 -1
  294. package/dist/core/types.js.map +0 -1
  295. package/dist/core/weights.d.ts.map +0 -1
  296. package/dist/core/weights.js.map +0 -1
  297. /package/dist/{core → engine}/agents.d.ts +0 -0
  298. /package/dist/{core → engine}/agents.js +0 -0
  299. /package/dist/{core → engine}/attention.d.ts +0 -0
  300. /package/dist/{core → engine}/attention.js +0 -0
  301. /package/dist/{core → engine}/causality.d.ts +0 -0
  302. /package/dist/{core → engine}/causality.js +0 -0
  303. /package/dist/{core → engine}/conditions.d.ts +0 -0
  304. /package/dist/{core → engine}/conditions.js +0 -0
  305. /package/dist/{core → engine}/currents.js +0 -0
  306. /package/dist/{core → engine}/dock.d.ts +0 -0
  307. /package/dist/{core → engine}/dock.js +0 -0
  308. /package/dist/{core → engine}/feedback.d.ts +0 -0
  309. /package/dist/{core → engine}/feedback.js +0 -0
  310. /package/dist/{core → engine}/field-snapshot.d.ts +0 -0
  311. /package/dist/{core → engine}/field-snapshot.js +0 -0
  312. /package/dist/{core → engine}/field-store.d.ts +0 -0
  313. /package/dist/{core → engine}/field-store.js +0 -0
  314. /package/dist/{core → engine}/field.d.ts +0 -0
  315. /package/dist/{core → engine}/fieldline-seeds.d.ts +0 -0
  316. /package/dist/{core → engine}/fieldline-seeds.js +0 -0
  317. /package/dist/{core → engine}/fieldlines.d.ts +0 -0
  318. /package/dist/{core → engine}/flow.d.ts +0 -0
  319. /package/dist/{core → engine}/flow.js +0 -0
  320. /package/dist/{core → engine}/formations.d.ts +0 -0
  321. /package/dist/{core → engine}/formations.js +0 -0
  322. /package/dist/{core → engine}/governance.d.ts +0 -0
  323. /package/dist/{core → engine}/governance.js +0 -0
  324. /package/dist/{core → engine}/heatmap.d.ts +0 -0
  325. /package/dist/{core → engine}/host-headless.d.ts +0 -0
  326. /package/dist/{core → engine}/host-headless.js +0 -0
  327. /package/dist/{core → engine}/lane-registry.d.ts +0 -0
  328. /package/dist/{core → engine}/lane-registry.js +0 -0
  329. /package/dist/{core → engine}/projection-agent-json.d.ts +0 -0
  330. /package/dist/{core → engine}/projection-agent-json.js +0 -0
  331. /package/dist/{core → engine}/query-lens.d.ts +0 -0
  332. /package/dist/{core → engine}/query-lens.js +0 -0
  333. /package/dist/{core → engine}/registry.d.ts +0 -0
  334. /package/dist/{core → engine}/registry.js +0 -0
  335. /package/dist/{core → engine}/render-backend.d.ts +0 -0
  336. /package/dist/{core → engine}/render-backend.js +0 -0
  337. /package/dist/{core → engine}/reservoir.d.ts +0 -0
  338. /package/dist/{core → engine}/reservoir.js +0 -0
  339. /package/dist/{core → engine}/scalar-grid.d.ts +0 -0
  340. /package/dist/{core → engine}/scalar-grid.js +0 -0
  341. /package/dist/{core → engine}/shadow.d.ts +0 -0
  342. /package/dist/{core → engine}/shadow.js +0 -0
  343. /package/dist/{core → engine}/streamlines.d.ts +0 -0
  344. /package/dist/{core → engine}/streamlines.js +0 -0
  345. /package/dist/{core → engine}/surface.d.ts +0 -0
  346. /package/dist/{core → engine}/surface.js +0 -0
  347. /package/dist/{core → engine}/temporal.d.ts +0 -0
  348. /package/dist/{core → engine}/temporal.js +0 -0
  349. /package/dist/{core → engine}/thermo.d.ts +0 -0
  350. /package/dist/{core → engine}/thermo.js +0 -0
  351. /package/dist/{core → engine}/types.js +0 -0
  352. /package/dist/{core → engine}/weights.d.ts +0 -0
  353. /package/dist/{core → engine}/weights.js +0 -0
  354. /package/dist/{core → math}/math.d.ts +0 -0
  355. /package/dist/{core → math}/math.js +0 -0
@@ -84,6 +84,16 @@ export interface Particle {
84
84
  */
85
85
  orient?: number;
86
86
  spin?: number;
87
+ /**
88
+ * OPTIONAL ACCELERATION LANE (velocity-Verlet, #659): the previous step's acceleration
89
+ * a(t) = Δv/dt, stored so the next position full-step can take `x += v·dt + ½·a·dt²` and the
90
+ * velocity half-step can average `½·(a + a′)·dt`. Only the `'velocity-verlet'` integrator ever
91
+ * writes these; undefined ⇒ 0 ⇒ the default engine never materializes them (the z-lane
92
+ * discipline — byte-identical when unused).
93
+ */
94
+ ax?: number;
95
+ ay?: number;
96
+ az?: number;
87
97
  /** inertial mass — 1 = nominal (§21). */
88
98
  m: number;
89
99
  /** ∈ [0,1]; drives color (toward accent), size, and glow (§2.2). */
@@ -136,12 +146,111 @@ export interface AtomPayload {
136
146
  * and conservation are later refinements.)
137
147
  */
138
148
  export type BodyAuthority = 'anchored' | 'kinematic' | 'dynamic';
149
+ /**
150
+ * A body's FIRST-CLASS IDENTITY (substrate critical path). A stable, structured handle for referring to
151
+ * a body across frames, snapshots, diffs, and relationships — decoupled from object reference and from
152
+ * display text. `id` is the stable primary key: it MUST be unique within a field and MUST NOT change for
153
+ * the life of the body. The rest is optional metadata that lets a consumer group / route bodies (e.g. a
154
+ * bridge keying host meshes off `host`, or an agent filtering by `kind`).
155
+ *
156
+ * DOCTRINE — identity is NOT:
157
+ * · display text (a heading's words are not its identity — they can change while identity holds);
158
+ * · necessarily a DOM `id` (a DOM id is one *source* of a stable id, not the concept);
159
+ * · an object reference (references don't survive a rescan or a serialize/replay round-trip).
160
+ * Snapshots, diffs, and relationships key on `identity.id`. When a body carries no supplied identity, the
161
+ * engine DERIVES a stable one deterministically (the element's DOM id, else a monotonic `body-N` counter —
162
+ * never `Math.random`, which is banned on the reproducible paths) so identity is always present and stable.
163
+ */
164
+ export interface FieldBodyIdentity {
165
+ /** the stable primary key — unique within the field, constant for the body's life. Equals the reading's
166
+ * top-level `id` (back-compat). Snapshot/diff/replay/relationships key on this. */
167
+ id: string;
168
+ /** optional grouping namespace (e.g. an app/module the body belongs to). Free-form; opaque to the engine. */
169
+ namespace?: string;
170
+ /** optional kind/type tag (e.g. `'card'`, `'heading'`, `'agent'`). Free-form; opaque to the engine. */
171
+ kind?: string;
172
+ /** optional host/owner tag (e.g. a renderer or view that owns the body's rendered object). Free-form. */
173
+ host?: string;
174
+ }
175
+ /** Who directed a focus deposit. Open union so multi-agent tags ride along (e.g. `'agent:planner'`). */
176
+ export type FocusSource = 'operator' | 'agent' | 'system' | (string & {});
177
+ /** One focus deposit's parameters. */
178
+ export interface FocusInput {
179
+ /** the deposit's weight (default `1`). Accumulates decay-then-add onto the source's running mass. */
180
+ amount?: number;
181
+ /** who directed it (default `'system'` on the raw handle; the host stamps `'operator'` / `'agent'`). */
182
+ source?: FocusSource;
183
+ /** the deposit's half-life, in the field's simulation-clock unit (`env.t` SECONDS, matching
184
+ * {@link FocusState}`.time`) — how fast this attention goes stale (default ~8). */
185
+ halfLife?: number;
186
+ /** the deposit's timestamp on the simulation clock (`env.t` seconds; default the field's current `env.t`);
187
+ * backdates a stale focus. Same unit as {@link FocusState}`.time`. */
188
+ at?: number;
189
+ }
190
+ /** One source's decayed contribution to a body's salience — the provenance split (who is focused here). */
191
+ export interface FocusSourceShare {
192
+ source: FocusSource;
193
+ /** the source's decayed weight at read time, clamped `0..1`. */
194
+ weight: number;
195
+ /** when this source last deposited (simulation clock). */
196
+ updatedAt: number;
197
+ }
198
+ /** A focused entity in a {@link FocusState} reading — who (sources), how hot (salience), where (identity). */
199
+ export interface FocusEntry {
200
+ /** the entity's addressable id (the focus target; equals `identity.id`). */
201
+ target: string;
202
+ /** the entity's first-class identity (see {@link FieldBodyIdentity}) — the id for a write-back. */
203
+ identity: FieldBodyIdentity;
204
+ /** net decayed directed focus, clamped `0..1`. */
205
+ salience: number;
206
+ /** the per-source split (present in an agent view only with `read:focus`). */
207
+ sources: FocusSourceShare[];
208
+ /** the most recent deposit's timestamp (simulation clock). */
209
+ updatedAt: number;
210
+ }
211
+ /** Options for {@link FieldHandle.focusState} — bound the sharp-tip digest. All optional. */
212
+ export interface FocusReadOptions {
213
+ /** max entries returned (default `8`). */
214
+ limit?: number;
215
+ /** drop entries below this salience (default `0.05`). */
216
+ threshold?: number;
217
+ /** rank + filter by ONE source's decayed weight instead of net salience. */
218
+ source?: FocusSource;
219
+ }
220
+ /** The current-focus digest — small, ranked, thresholded; cheap to push into an agent turn. */
221
+ export interface FocusState {
222
+ frame: number;
223
+ /** the simulation clock at read time (matches {@link FieldQueryResult}`.time`). */
224
+ time: number;
225
+ /** entries ranked desc by salience (or by `opts.source` weight), thresholded, capped. */
226
+ entries: FocusEntry[];
227
+ }
228
+ /** The `focus` discrete event — the write-back channel AND an append-only, timestamped provenance receipt. */
229
+ export interface FocusEvent {
230
+ /** the focused entity's addressable id. */
231
+ target: string;
232
+ identity: FieldBodyIdentity;
233
+ /** who directed this deposit. */
234
+ source: FocusSource;
235
+ /** this deposit's weight. */
236
+ amount: number;
237
+ /** the entity's net decayed salience after this deposit, clamped `0..1`. */
238
+ salience: number;
239
+ /** this source's decayed running mass after this deposit, clamped `0..1`. */
240
+ sourceSalience: number;
241
+ frame: number;
242
+ time: number;
243
+ }
139
244
  /**
140
245
  * A registered DOM element acting as a force source (§3.1). Parsed from
141
246
  * `data-*` attributes; the runtime fields are refreshed each scan/frame.
142
247
  */
143
248
  export interface Body {
144
249
  el: HTMLElement;
250
+ /** FIRST-CLASS IDENTITY (see {@link FieldBodyIdentity}). Supplied via `addBody({ identity })` or the
251
+ * `identify` field option, else lazily DERIVED and cached the first time the body is keyed. Once
252
+ * resolved it is stable for the body's life; snapshots/diff/replay/relationships key on `identity.id`. */
253
+ identity?: FieldBodyIdentity;
145
254
  /** space-joined force ids from `data-body` (they compose, §4). */
146
255
  tokens: Token[];
147
256
  /** who owns this body's position (`data-authority`); default `'anchored'`. See {@link BodyAuthority}. */
@@ -183,6 +292,10 @@ export interface Body {
183
292
  * box, not its centre, so matter gathers in a shell around the shape (field-systems
184
293
  * Stage C). Undefined ⇒ point source (the default). */
185
294
  shaped?: boolean;
295
+ /** `data-charge-gated` (opt-in, #711) — restrict `fieldflow` to *charged* matter (`charge ≠ 0`),
296
+ * modelling magnetized plasma tied to the field line. Undefined/false ⇒ the default neutral-medium
297
+ * advection (fieldflow transports ALL matter). Only read by the `fieldflow` force. */
298
+ chargeGated?: boolean;
186
299
  /** `data-species` — the species tag this body stamps on matter it *emits* (a `spawn` source),
187
300
  * so multiple ecologies (pollen vs seeds vs spores) can share one field. Undefined ⇒ 0. */
188
301
  species?: number;
@@ -221,6 +334,10 @@ export interface Body {
221
334
  warpHas?: boolean;
222
335
  /** source mass M for `gravity`/`charge` (§20.10/§21). */
223
336
  M: number;
337
+ /** INERTIAL mass (substrate momentum, #872) — how hard the body is to *move*, distinct from `M` (how
338
+ * strongly it *emits*). Undefined ⇒ nominal 1 ⇒ byte-identical. Populated (∝ rendered area, clamped)
339
+ * only under `mass: 'area'`; the dynamic-body recoil integrator divides by `inertia ?? M`. */
340
+ inertia?: number;
224
341
  cx: number;
225
342
  cy: number;
226
343
  hw: number;
@@ -236,6 +353,12 @@ export interface Body {
236
353
  d: number;
237
354
  /** conserved-attention effective-strength multiplier (§2.4); 1 = neutral. */
238
355
  attn?: number;
356
+ /** EXPERIMENTAL (focus substrate): this body's net decayed directed focus ∈ [0,1], joined from the
357
+ * focus ledger each frame; `undefined` when unfocused (the fast path). Surfaces as `metrics.salience`. */
358
+ salience?: number;
359
+ /** EXPERIMENTAL (focus substrate): the focus-well strength multiplier derived from `salience` (≥1,
360
+ * clamped), applied at the integrator; `undefined` = neutral (preserves the `mul === 1` fast path). */
361
+ focusMul?: number;
239
362
  /** fractional-emission accumulator for a budgeted [S] source (`spawn`) — carries the
240
363
  * sub-1/frame remainder when the rate is clamped to `cap / life`. Runtime state. */
241
364
  emitAcc?: number;
@@ -373,11 +496,22 @@ export interface Env {
373
496
  * semi-implicit Euler with per-frame decay (the default — unchanged). `'fixed'` is the opt-in
374
497
  * fixed-timestep integrator: additive force impulses and the `FRICTION`/`HEAT_DECAY` decays scale
375
498
  * with `dt`, so motion is frame-rate independent. At `dt === 1` (the reference rate, and every
376
- * golden/conformance run) the two are byte-identical, so opting in never moves the golden. */
499
+ * golden/conformance run) the two are byte-identical, so opting in never moves the golden.
500
+ * `'velocity-verlet'` (#659) is the opt-in second-order scheme: the position full-step uses the
501
+ * previous step's stored acceleration, the force pass evaluates a′ at the updated position, and
502
+ * the velocity takes the half-step average — see the integrator for the exact math and the
503
+ * velocity-dependence / kinematic-force approximations. It changes trajectories BY DESIGN once
504
+ * opted into; the default path never engages it. */
377
505
  integrator?: IntegratorMode;
506
+ /** INTERNAL (velocity-Verlet only): set by the central `applyForce` when a *kinematic*
507
+ * (velocity-REPLACING) force actually changed the current particle's velocity, and reset by the
508
+ * integrator at the top of each particle's force pass. A reflection / relaunch / teleport is a
509
+ * discontinuity, not an acceleration — the Verlet half-step average is skipped for that particle
510
+ * that step. Never touched on the `'legacy'`/`'fixed'` paths. */
511
+ kinTouch?: boolean;
378
512
  }
379
513
  /** The integration scheme for the field (see {@link Env.integrator}). */
380
- export type IntegratorMode = 'legacy' | 'fixed';
514
+ export type IntegratorMode = 'legacy' | 'fixed' | 'velocity-verlet';
381
515
  /**
382
516
  * A single force's contribution to one agent in one step, in one channel (substrate doc 04).
383
517
  * The unit the diagnostics (`causality`/`prediction`), Field Query, and Causal Replay consume:
@@ -526,6 +660,53 @@ export type ConditionRegistry = Record<string, Condition>;
526
660
  export type OverlayMode = 'off' | 'streamlines' | 'force-vectors' | 'field-lines' | 'grid' | 'temperature' | 'energy' | 'path' | 'data';
527
661
  /** One reading, or an additive stack of readings, for `setOverlay` / `FieldOptions.overlay`. */
528
662
  export type OverlayInput = OverlayMode | readonly OverlayMode[];
663
+ /**
664
+ * Consumable field-resource budgets — upper bounds a host/session/user/app sets on what the field is
665
+ * PERMITTED to spend, distinct from what doctrine *allows* (that is governance — static lint). Each is
666
+ * optional; an unset budget means "unbounded / engine default". Values are normalized `0..1` unless the
667
+ * one-line note says otherwise. Carried on {@link FieldPolicy.budgets}.
668
+ *
669
+ * WIRED today: `motion` (folds into the effective motion allowance alongside reduced-motion + perf
670
+ * pressure) and `privacy` (gates body `data` in snapshots). The rest are DECLARED-not-yet-enforced —
671
+ * accepted and carried on the policy for host/tooling introspection, wired as their consumers land.
672
+ */
673
+ export interface FieldBudgets {
674
+ /** WIRED. `0..1` cap on how much motion the field may express; `0` behaves as reduced-motion (frozen). */
675
+ motion?: number;
676
+ /** DECLARED. `0..1` cap on applied force magnitude — the share of the impulse budget matter may absorb. */
677
+ force?: number;
678
+ /** DECLARED. `0..1` cap on conserved-attention spend (§2.4) — the finite focus budget. */
679
+ attention?: number;
680
+ /** DECLARED. `0..1` cap on thermal/heat accumulation the field may carry. */
681
+ thermal?: number;
682
+ /** DECLARED. `0..1` cap on render cost the field may spend (draw layers / fill). */
683
+ render?: number;
684
+ /** WIRED. `0..1` privacy budget; below the `PRIVACY_DATA_THRESHOLD` (0.5) snapshots withhold body `data`. */
685
+ privacy?: number;
686
+ /** DECLARED. `0..1` accessibility floor — the minimum non-motion legibility the field must preserve. */
687
+ accessibility?: number;
688
+ /** DECLARED. `0..1` cap on how much field state agent readers (query/snapshot/agent-json) may consume. */
689
+ agentRead?: number;
690
+ }
691
+ /**
692
+ * Runtime FIELD POLICY — what THIS host / session / user / app PERMITS, evaluated live. Distinct lane
693
+ * from GOVERNANCE (what doctrine allows — static lint): policy can only tighten, never loosen, the
694
+ * accessibility floor (reduced-motion always wins; a policy can lower motion but never raise it above
695
+ * what the host/user reduced-motion state allows). Set at creation via {@link FieldOptions.policy} and
696
+ * live via {@link FieldHandle.setPolicy}; read via {@link FieldHandle.policy}. Purely additive — a field
697
+ * with no policy behaves exactly as before.
698
+ */
699
+ export interface FieldPolicy {
700
+ /** permit body `data` to appear in snapshots (default: fall through to `FieldSnapshotOptions.includeData`). */
701
+ allowBodyDataInSnapshots?: boolean;
702
+ /** permit motion-expressing projections/animation at all; `false` pins the effective motion budget to 0. */
703
+ allowMotionProjection?: boolean;
704
+ /** `0..1` host/session cap on motion; folded (via `min`) with reduced-motion + perf pressure into the
705
+ * effective motion allowance the integrator/easing path reads. Reduced-motion can only lower it. */
706
+ maxMotionBudget?: number;
707
+ /** consumable-resource budgets (see {@link FieldBudgets}). */
708
+ budgets?: Partial<FieldBudgets>;
709
+ }
529
710
  export interface FieldOptions {
530
711
  /** travelling accent color (§9). */
531
712
  accent?: string;
@@ -540,11 +721,14 @@ export interface FieldOptions {
540
721
  */
541
722
  depth?: number;
542
723
  /** the integration scheme (substrate doc 04 §Step 3); default `'legacy'`. `'fixed'` opts into the
543
- * frame-rate-independent fixed-timestep integrator (additive impulses and decay scale with `dt`).
544
- * Identical to legacy at the reference frame rate — see {@link Env.integrator}. */
724
+ * frame-rate-independent fixed-timestep integrator (additive impulses and decay scale with `dt`);
725
+ * identical to legacy at the reference frame rate. `'velocity-verlet'` (#659) opts into the
726
+ * second-order velocity-Verlet scheme (higher positional accuracy; trajectories differ by
727
+ * design). See {@link Env.integrator}. */
545
728
  integrator?: IntegratorMode;
546
- /** draw the background Currents (§24); default true. Set false for the bare
547
- * free-particle field with no carrier waves. */
729
+ /** draw the background Currents (§24); default **false** (opt-in, #979 — the signals-first
730
+ * companion to `render: 'none'`). A bare field has no carrier waves; set true for the ambient
731
+ * resting structure + the bound shimmer reservoir. */
548
732
  waves?: boolean;
549
733
  /** wave layout style: `'linear'` (default horizontal lines) or `'circular'` (concentric orbits around a center). */
550
734
  waveStyle?: 'linear' | 'circular';
@@ -571,13 +755,87 @@ export interface FieldOptions {
571
755
  * dots), 'streamlines' (draw the force field itself — diagnostic, REPLACES the dots),
572
756
  * 'flow' (the dots AND the streamlines drawn together in the one underlay canvas —
573
757
  * particles drifting along the visible flow, with no separate front surface and no
574
- * `mix-blend`, so it stays a single cheap layer). */
575
- render?: 'dots' | 'trails' | 'links' | 'metaballs' | 'voronoi' | 'streamlines' | 'flow' | 'none';
576
- /** first-class mass (§21.3): when true, particle mass ∝ size and body forces
577
- * accelerate by `a = F/m` (heavier matter moves less). Default false (unit mass). */
758
+ * `mix-blend`, so it stays a single cheap layer), 'knockout' (figure-ground inversion:
759
+ * an accent field wash with matter punched out as negative space — clip the canvas to
760
+ * type with a host CSS mask for the field-inside-letters treatment, #667), 'redshift'
761
+ * (dots tinted by spectral shift — Doppler from radial velocity + gravitational red
762
+ * near body wells, #668), 'blackbody' (dots tinted by energy on a thermal ramp, ember
763
+ * → white → blue-white, #669), 'depth' (the z lane made visible: far-to-near painter's
764
+ * sorting, perspective parallax, defocus with distance — pairs with `depth > 0`, #670). */
765
+ render?: 'dots' | 'trails' | 'links' | 'metaballs' | 'voronoi' | 'streamlines' | 'flow' | 'knockout' | 'redshift' | 'blackbody' | 'depth' | 'none';
766
+ /**
767
+ * DECLARED render reference point (Wallpaper Rule, #975): the center of the cool→warm heat
768
+ * vignette the `dots`/`depth` swarm is tinted against — a body far from this point reads warm,
769
+ * a body near it reads cool. Given as viewport FRACTIONS `{ x, y }` ∈ [0,1] (resolved to pixels
770
+ * against the live canvas each frame, so it tracks resize). Formerly a hardcoded `(W/2, H·0.4)`
771
+ * painted into the draw path (a content-independent "gray debt"): the default `{ x: 0.5, y: 0.4 }`
772
+ * reproduces it exactly, so the default render is byte-identical. Only affects the `dots`/`depth`
773
+ * render modes. **Experimental.** */
774
+ heatCenter?: {
775
+ x: number;
776
+ y: number;
777
+ };
778
+ /**
779
+ * DECLARED render reference point (Wallpaper Rule, #975): the OBSERVER the `redshift` mode
780
+ * measures each particle's radial velocity against — receding matter reddens, approaching matter
781
+ * blues, at-rest at the observer. Given as viewport FRACTIONS `{ x, y }` ∈ [0,1] (resolved to
782
+ * pixels against the live canvas each frame). Formerly a hardcoded `(W/2, H/2)` (a "gray debt"):
783
+ * the default `{ x: 0.5, y: 0.5 }` reproduces it exactly, so the `redshift` render is byte-identical.
784
+ * Only affects the `redshift` render mode. **Experimental.** */
785
+ redshiftObserver?: {
786
+ x: number;
787
+ y: number;
788
+ };
789
+ /**
790
+ * DECLARED render reference point (Wallpaper Rule, #975): the perspective focal length (in CSS px)
791
+ * of the `depth` mode's camera — the size/parallax recession the z lane is projected through. A
792
+ * larger focal flattens the perspective (a longer lens); a smaller one exaggerates it. Formerly a
793
+ * hardcoded `FOCAL = 480` (a "gray debt"): the default `480` reproduces it exactly, so the `depth`
794
+ * render is byte-identical. Only affects the `depth` render mode (pairs with `depth > 0`).
795
+ * **Experimental.** */
796
+ depthFocal?: number;
797
+ /**
798
+ * DECLARED page-layout reference (Wallpaper Rule, #975): how the density `heatmap` glow fades out
799
+ * as the page scrolls past the hero. `start` is the scroll position (in VIEWPORTS, `scrollY / H`)
800
+ * at which the fade begins, `span` is how many viewports it takes to reach fully transparent — the
801
+ * layer is full above `start·H` and gone by `(start + span)·H`. Formerly a hardcoded
802
+ * `(1.15 - scrollY/H)/0.85` baked into a core draw path — a "content = first viewport" assumption
803
+ * (a "gray debt"): the default `{ start: 0.3, span: 0.85 }` reproduces it exactly (the historical
804
+ * curve is full through `1.15 - 0.85 = 0.3` viewports and gone by `1.15`), so the heatmap glow is
805
+ * byte-identical. Set a large `span` to disable the scroll fade. Only affects the density `heatmap`
806
+ * layer. **Experimental.** */
807
+ heatmapFade?: {
808
+ start: number;
809
+ span: number;
810
+ };
811
+ /** first-class mass (§21.3): when true, particle mass ∝ size and body forces accelerate by `a = F/m`
812
+ * (heavier matter moves less). Also gives DYNAMIC bodies inertial mass ∝ rendered area (#872), so a
813
+ * big heading recoils slowly and a small tag snaps. Default false (unit mass, byte-identical). */
578
814
  mass?: boolean;
815
+ /** Newtonian own-emission reaction (substrate momentum, #873): when true, a DYNAMIC body feels the
816
+ * equal-and-opposite of the net impulse it imparts to nearby matter (a directional emitter recoils
817
+ * like a rocket; reciprocity closes through *motion*, not just feedback). Best paired with `mass`
818
+ * (recoil ÷ inertial mass). Default false ⇒ byte-identical. **Experimental.** */
819
+ reaction?: boolean;
579
820
  /** strength of particle-to-particle separation/repulsion force (0 to 1, default 0). */
580
821
  separation?: number;
822
+ /**
823
+ * DECLARED ambient bias (Wallpaper Rule, #978): the resting `ambient` formation's tangential
824
+ * swirl injected into `attract` bodies (`Formation.orbit`) — the gentle spiral free matter
825
+ * traces around a well at rest. Formerly a hardcoded `0.1` painted into the default formation
826
+ * preset (a "gray debt": a content-independent constant inside an honest feature). Now a
827
+ * documented, opt-in dial with the historical value as its default, so behavior is unchanged;
828
+ * set `0` for a purely radial resting attract (no spiral), or raise it for a stronger orbit.
829
+ * Applies to the `ambient` formation only (the section formations keep their authored presets);
830
+ * `<field-root ambient-orbit>`. Default `0.1`. */
831
+ ambientOrbit?: number;
832
+ /**
833
+ * DECLARED ambient bias (Wallpaper Rule, #978): the resting `ambient` formation's `wander`
834
+ * term — the per-particle drift that keeps resting matter alive rather than frozen. Formerly a
835
+ * hardcoded `1.0` in the default formation preset. Now a documented, opt-in dial defaulting to
836
+ * the historical value (behavior unchanged); lower it for a calmer rest, `0` to still the drift.
837
+ * Applies to the `ambient` formation only; `<field-root ambient-wander>`. Default `1.0`. */
838
+ ambientWander?: number;
581
839
  /** color template for the travelling accent (§9): a built-in name
582
840
  * (`'ours'` · `'heatmap'` · `'infrared'` · `'spectrum'`) or custom hex stops. */
583
841
  palette?: string | readonly string[];
@@ -613,6 +871,17 @@ export interface FieldOptions {
613
871
  * provides the canvas, core only draws. Default unset → no overlay surface.
614
872
  */
615
873
  overlayCanvas?: HTMLCanvasElement;
874
+ /**
875
+ * Field Surfaces (overlay placement, #676): a lazy alternative to `overlayCanvas`. When no
876
+ * `overlayCanvas` is bound, core calls this provider ONCE — the first time an overlay reading actually
877
+ * becomes active (a non-`off` `setOverlay`, or `setRender` leaving `'none'` with a reading already
878
+ * set) — to obtain the surface. This lets a host (e.g. `<field-root>`) defer creating its
879
+ * full-viewport, mix-blend light-DOM canvas until an overlay is switched on, so the common
880
+ * `overlay: off` path never puts a canvas into the compositing tree at boot. Return `null` to decline
881
+ * (stays surface-less). Ignored when `overlayCanvas` is set. Keeps core DOM-free — the host still owns
882
+ * the element and its CSS placement.
883
+ */
884
+ overlayCanvasProvider?: () => HTMLCanvasElement | null;
616
885
  /** initial overlay visualization mode (Field Surfaces); default `'off'`. */
617
886
  overlay?: OverlayInput;
618
887
  /**
@@ -658,6 +927,21 @@ export interface FieldOptions {
658
927
  * you; inject a custom host for a headless renderer / different document / tests.
659
928
  */
660
929
  host?: FieldHost;
930
+ /**
931
+ * Initial runtime {@link FieldPolicy} — what this host/session/user/app PERMITS (runtime rules),
932
+ * distinct from governance (what doctrine allows). Change it live with {@link FieldHandle.setPolicy}.
933
+ * Purely additive — omit for the unbounded default. */
934
+ policy?: FieldPolicy;
935
+ /**
936
+ * FIRST-CLASS IDENTITY resolver (substrate critical path): derive a {@link FieldBodyIdentity} for a
937
+ * DOM-scanned body from its element. Called once per body, the first time the body is keyed; the
938
+ * returned identity is cached and used for query/snapshot/diff/replay/relationship keying. Return
939
+ * `undefined` (or omit the option) to fall back to the default derivation (the element's DOM `id`,
940
+ * else a monotonic `body-N`). The `id` a resolver returns MUST be unique within the field and stable
941
+ * for the body's life. Programmatic `addBody({ identity })` overrides this. Purely additive — a field
942
+ * with no `identify` behaves exactly as before.
943
+ */
944
+ identify?: (el: HTMLElement) => FieldBodyIdentity | undefined;
661
945
  }
662
946
  /** Per-element feedback values the engine produces each frame (Phase D3 seam). */
663
947
  export interface FeedbackChannels {
@@ -665,7 +949,7 @@ export interface FeedbackChannels {
665
949
  density?: number;
666
950
  /** the ambient heatmap density at the body → `--field-heatmap-density`. */
667
951
  heatmapDensity?: number;
668
- /** sink accretion fill ∈ [0,1] → `--load` / `--mass`. */
952
+ /** sink accretion fill ∈ [0,1] → `--load`. */
669
953
  load?: number;
670
954
  /** cross-boundary lit signal ∈ [0,1] → `--lit` + thresholded `field:lit` / `field:dim`. */
671
955
  lit?: number;
@@ -708,6 +992,11 @@ export interface AgentHandle {
708
992
  export interface BodySpec {
709
993
  /** the force ids this body emits (space-joined string or array), e.g. `'attract swirl'`. */
710
994
  tokens: string | readonly string[];
995
+ /** FIRST-CLASS IDENTITY for this programmatic body (see {@link FieldBodyIdentity}). Supply a stable
996
+ * `id` (unique in the field) plus optional `namespace`/`kind`/`host`, so snapshots/diff/replay and a
997
+ * bridge can reference this body by identity rather than the returned handle. A bare string is shorthand
998
+ * for `{ id }`. Omitted ⇒ the engine derives a stable synthetic `body-N`. */
999
+ identity?: FieldBodyIdentity | string;
711
1000
  /** who owns this body's position; default `'anchored'`. See {@link BodyAuthority}. */
712
1001
  authority?: BodyAuthority;
713
1002
  /** overall force magnitude (scales every token). */
@@ -814,8 +1103,12 @@ export interface FieldQuery {
814
1103
  }
815
1104
  /** A body as seen by a query — identity, box, active tokens, and its measured metrics/dimensions. */
816
1105
  export interface FieldBodyReading {
817
- /** stable id: the element's `id` when present, else a per-field synthetic (`body-N`). */
1106
+ /** stable id: the element's `id` when present, else a per-field synthetic (`body-N`). Equals
1107
+ * `identity.id`. Kept as the top-level field for back-compat; new consumers may read `identity`. */
818
1108
  id: string;
1109
+ /** the body's resolved FIRST-CLASS IDENTITY (see {@link FieldBodyIdentity}). `identity.id === id`;
1110
+ * `namespace`/`kind`/`host` carry any supplied structured metadata. Always present. */
1111
+ identity: FieldBodyIdentity;
819
1112
  /** the body's box in field coordinates, when measured. */
820
1113
  rect?: FieldRect;
821
1114
  /** the composed force ids (the `data-body` tokens). */
@@ -881,6 +1174,19 @@ export interface FieldQueryResult {
881
1174
  /** the lens id this reading was scoped through, when a `lens` was supplied (substrate query phase 2). */
882
1175
  lens?: string;
883
1176
  }
1177
+ /**
1178
+ * A named **snapshot profile** — a concrete inclusion preset for {@link FieldHandle.snapshot}, resolved
1179
+ * to the TIGHTEST (most private) combination of its base inclusions, any explicit `include*` flags, and
1180
+ * the runtime {@link FieldPolicy} privacy budget. A profile can only tighten a call; it never widens past
1181
+ * what policy allows.
1182
+ *
1183
+ * - `'debug'` — everything: particles, relationships, influences, and body `data` (still gated by policy).
1184
+ * - `'agent'` — the Software-Agent read: stable ids + metrics + relationships + influence attribution +
1185
+ * projections, but NO opaque body `data` (raw/user-identifying payloads withheld regardless of `includeData`).
1186
+ * - `'bug-report'` — structural + versions (relationships + influences), no user data.
1187
+ * - `'public'` — minimal: ids + shape (bodies/metrics/projections), no relationships, influences, or data.
1188
+ */
1189
+ export type SnapshotProfile = 'debug' | 'agent' | 'bug-report' | 'public';
884
1190
  /** Options for {@link FieldHandle.snapshot}. */
885
1191
  export interface FieldSnapshotOptions {
886
1192
  /** include the raw particle pool (heavier; off by default for lightweight exports). */
@@ -892,10 +1198,65 @@ export interface FieldSnapshotOptions {
892
1198
  /** include per-body force attribution (each body's own forces at its centre, via the impulse
893
1199
  * accumulator) so a later `replay()` can derive `cause: 'force'` steps. Off by default. */
894
1200
  includeInfluences?: boolean;
1201
+ /** apply a named {@link SnapshotProfile} preset. Composes with the explicit `include*` flags and the
1202
+ * {@link FieldPolicy} privacy budget, always resolving to the TIGHTEST (most private) result — a
1203
+ * profile can never widen past what policy or an explicit deny allows. */
1204
+ profile?: SnapshotProfile;
1205
+ }
1206
+ /**
1207
+ * A scoped read CAPABILITY an {@link AgentFieldView} grants. Each names one dimension of the field's
1208
+ * read surface; a capability set is an allow-list — a dimension the caps don't include is stripped from
1209
+ * every reading (it tightens, never widens). Read-only throughout — there is no write capability, because
1210
+ * *agent-readable is not agent-writable* (see `docs/canonical/agent-consumption-model.md`).
1211
+ */
1212
+ export type AgentCapability = 'read:metrics' | 'read:relationships' | 'read:influences' | 'read:snapshots' | 'read:body-data' | 'read:projections' | 'read:diagnostics' | 'read:replay'
1213
+ /** EXPERIMENTAL: gates the {@link AgentFieldView.focusState} digest + the per-source provenance split.
1214
+ * The aggregate per-body `salience` still rides `query()`/`snapshot()` under the base grant. */
1215
+ | 'read:focus';
1216
+ /** Options for {@link FieldHandle.forAgent} — the capability grant + optional redaction list. */
1217
+ export interface AgentViewOptions {
1218
+ /** the capabilities this agent view grants. An allow-list: any dimension not listed is stripped from
1219
+ * every reading. An empty set yields the most-restricted view (ids + shape only). */
1220
+ capabilities: AgentCapability[];
1221
+ /** dotted paths stripped from every reading AFTER capability scoping (e.g. `'body.data'`, `'host.user'`,
1222
+ * `'metrics.temperature'`). `body.*` / `relationship.*` / `influence.*` / `projection.*` prefixes address
1223
+ * the per-entry shape of each list; a bare top-level key addresses the result/snapshot itself. */
1224
+ redactions?: string[];
1225
+ }
1226
+ /**
1227
+ * A READ-ONLY facade over a field, scoped to a set of {@link AgentCapability}s — the surface a Software
1228
+ * Agent uses to read the field safely. It exposes ONLY scoped `query()` / `snapshot()` (and `replay()`
1229
+ * when `read:replay` is granted). It has NO mutation methods — no `applyForce`, no `addBody`, no
1230
+ * `setPolicy` — enforced by the facade's very shape: *agent-readable is not agent-writable*. Every
1231
+ * reading is tightened to the granted capabilities, then any `redactions` paths are stripped, and the
1232
+ * result can never widen past what the field's {@link FieldPolicy} already permits.
1233
+ */
1234
+ export interface AgentFieldView {
1235
+ /** the granted capabilities (a frozen copy). */
1236
+ readonly capabilities: readonly AgentCapability[];
1237
+ /** the redaction paths (a frozen copy). */
1238
+ readonly redactions: readonly string[];
1239
+ /** a capability-scoped, redacted {@link FieldQueryResult}. Dimensions the caps don't grant are absent
1240
+ * (no `read:influences` → no influences; no `read:relationships` → no relationships; etc.). */
1241
+ query(q?: FieldQuery): FieldQueryResult;
1242
+ /** a capability-scoped, redacted {@link FieldSnapshot}. Body `data` is withheld unless `read:body-data`
1243
+ * is granted (and policy permits it); a `profile`/`include*` request can only tighten from here. */
1244
+ snapshot(opts?: FieldSnapshotOptions): FieldSnapshot;
1245
+ /** narrate how the field changed between two snapshots — present ONLY when `read:replay` is granted
1246
+ * (otherwise `undefined`, so the facade's shape reflects the grant). */
1247
+ replay?(a: FieldSnapshot, b: FieldSnapshot, opts?: ReplayOptions): CausalReplay;
1248
+ /** the current-focus digest — present ONLY when `read:focus` is granted (otherwise `undefined`, so the
1249
+ * facade's shape reflects the grant). Read-only, like the rest of the view. The aggregate per-body
1250
+ * `salience` still rides `query()`/`snapshot()` metrics; this adds the ranked tip with the per-source
1251
+ * provenance split (who is focused where). */
1252
+ focusState?(opts?: FocusReadOptions): FocusState;
895
1253
  }
896
1254
  /** A body captured in a {@link FieldSnapshot}. */
897
1255
  export interface FieldBodySnapshot {
1256
+ /** stable id — equals `identity.id`; snapshot/diff/replay key on it. */
898
1257
  id: string;
1258
+ /** the body's resolved FIRST-CLASS IDENTITY (see {@link FieldBodyIdentity}). Always present. */
1259
+ identity: FieldBodyIdentity;
899
1260
  /** who owns this body's position (see {@link BodyAuthority}); `'anchored'` by default. */
900
1261
  authority?: BodyAuthority;
901
1262
  /** the body's box in field coordinates (anchored bodies). */
@@ -1154,6 +1515,23 @@ export interface FieldHandle {
1154
1515
  * quality. The platform runtime forwards the governor's tier automatically; call it directly for a
1155
1516
  * custom quality policy. */
1156
1517
  setQualityTier(tier: number): void;
1518
+ /** the field's current runtime {@link FieldPolicy} (a frozen copy). `{}` when none was set. */
1519
+ readonly policy: FieldPolicy;
1520
+ /** Replace the runtime {@link FieldPolicy} live — what this host/session/user/app PERMITS. This is a
1521
+ * REPLACE (not a merge): pass the full policy you want in effect (`{}` clears to the unbounded
1522
+ * default). Takes effect from the next frame. The motion budget it carries folds (via `min`) with
1523
+ * reduced-motion + perf pressure — reduced-motion always wins, so a policy can lower motion but never
1524
+ * raise it. The privacy budget gates body `data` in `snapshot()`. */
1525
+ setPolicy(policy: FieldPolicy): void;
1526
+ /**
1527
+ * Derive a READ-ONLY {@link AgentFieldView} scoped to a set of {@link AgentCapability}s — the safe
1528
+ * surface a Software Agent uses to read the field. The returned facade exposes ONLY scoped
1529
+ * `query()` / `snapshot()` (and `replay()` when `read:replay` is granted); it has NO mutation methods.
1530
+ * Readings are tightened to the granted capabilities, then any `redactions` paths are stripped, and the
1531
+ * result can never widen past what the field's {@link FieldPolicy} already permits. Purely additive —
1532
+ * `forAgent` reads the same live field; it does not fork or copy it.
1533
+ */
1534
+ forAgent(opts: AgentViewOptions): AgentFieldView;
1157
1535
  /**
1158
1536
  * Switch the underlay render mode (§20.6) live — the surface behind content. `'none'` is the
1159
1537
  * signals-only mode (§13.7 / #297): drawing stops from the next frame while the simulation and
@@ -1161,7 +1539,7 @@ export interface FieldHandle {
1161
1539
  * backing store (the no-allocation guarantee belongs to fields CREATED with `render: 'none'`);
1162
1540
  * switching FROM `'none'` acquires the context lazily and sizes the backing store at that moment.
1163
1541
  */
1164
- setRender(mode: 'dots' | 'trails' | 'links' | 'metaballs' | 'voronoi' | 'streamlines' | 'flow' | 'none'): void;
1542
+ setRender(mode: 'dots' | 'trails' | 'links' | 'metaballs' | 'voronoi' | 'streamlines' | 'flow' | 'knockout' | 'redshift' | 'blackbody' | 'depth' | 'none'): void;
1165
1543
  /**
1166
1544
  * Render field READINGS on the OVERLAY surface — in front of page content (Field Surfaces). Pairs
1167
1545
  * with `setRender` (the underlay); set both for an immersive look. No-op unless the field was created
@@ -1342,13 +1720,32 @@ export interface FieldHandle {
1342
1720
  * name (e.g. `'scent'`) to keep an authored field independent. Read-write, lives in field px.
1343
1721
  */
1344
1722
  grid(name: string): ScalarGrid;
1723
+ /**
1724
+ * EXPERIMENTAL — deposit source-tagged, decaying **focus** onto a body by identity: the write side of
1725
+ * the shared attention channel. `focus('file:src/auth.ts', { source: 'operator' })`. Operator/host
1726
+ * attention is an input; an agent's is an output — the host relays `field.focus(id, { source: 'agent' })`
1727
+ * so the read-only agent view can never write. Deposits accumulate decay-then-add per source (decay is
1728
+ * `temporal.freshness`, the `env.t` clock — no `Date.now`); each fires the `focus` event and surfaces as
1729
+ * `metrics.salience` + in {@link focusState}. A string target ALWAYS records (retained until a body with
1730
+ * that id appears, then it binds). The only no-op is the absent-field case (e.g. a detached `<field-root>`).
1731
+ */
1732
+ focus(target: string | FieldBodyIdentity, input?: FocusInput): void;
1733
+ /**
1734
+ * EXPERIMENTAL — read the current-focus digest: the ranked, thresholded, capped "sharp tip" (a few
1735
+ * hundred bytes), small enough to push into an agent turn each turn. Net breadth — any body's focus
1736
+ * magnitude — rides `query()`/`snapshot()` via `metrics.salience`; this is the tip, with the per-source
1737
+ * provenance split (who is focused where).
1738
+ */
1739
+ focusState(opts?: FocusReadOptions): FocusState;
1345
1740
  /**
1346
1741
  * Subscribe to a discrete **field event** — the engine's host-agnostic push bus, for reacting to
1347
1742
  * *occurrences* instead of polling the continuous feedback channels each frame. Returns an
1348
1743
  * unsubscribe function. Plain data, no DOM (distinct from the `data-on` CustomEvent bindings a DOM
1349
- * host uses). Events: `absorb` / `release` — a `sink` body captured / let go of matter (the rising
1350
- * / falling edge of accretion), `{ body, count }`. Detection is lazy: a type with no listener costs
1351
- * nothing. (`contact`, `settle`, and per-particle `enter`·`exit` are the next slice — see #441.)
1744
+ * host uses). Events: `captured` / `released` — a `sink` body captured / let go of matter (the rising
1745
+ * / falling edge of accretion), `{ body, count }`; `enter` / `exit` — another body crossed INTO / OUT
1746
+ * of a body's `range`, `{ body, other }`; `met` — two bodies' boxes touched, `{ a, b }`; `focus` — a
1747
+ * `focus()` deposit landed (the write-back channel + provenance receipt), a flat {@link FocusEvent}.
1748
+ * Detection is lazy: a type with no listener costs nothing.
1352
1749
  */
1353
1750
  on<K extends FieldEventType>(type: K, cb: (e: FieldEventMap[K]) => void): () => void;
1354
1751
  /**