@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.
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/dist/agents/element-agent.d.ts +38 -0
- package/dist/agents/element-agent.d.ts.map +1 -0
- package/dist/agents/element-agent.js +70 -0
- package/dist/agents/element-agent.js.map +1 -0
- package/dist/agents/event-agent.d.ts +47 -0
- package/dist/agents/event-agent.d.ts.map +1 -0
- package/dist/agents/event-agent.js +82 -0
- package/dist/agents/event-agent.js.map +1 -0
- package/dist/agents/index.d.ts +17 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +57 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/region-agents.d.ts +40 -0
- package/dist/agents/region-agents.d.ts.map +1 -0
- package/dist/agents/region-agents.js +22 -0
- package/dist/agents/region-agents.js.map +1 -0
- package/dist/agents/relationship.d.ts +55 -0
- package/dist/agents/relationship.d.ts.map +1 -0
- package/dist/agents/relationship.js +40 -0
- package/dist/agents/relationship.js.map +1 -0
- package/dist/agents/user-agent.d.ts +57 -0
- package/dist/agents/user-agent.d.ts.map +1 -0
- package/dist/agents/user-agent.js +45 -0
- package/dist/agents/user-agent.js.map +1 -0
- package/dist/config/forces.config.d.ts +101 -0
- package/dist/config/forces.config.d.ts.map +1 -0
- package/dist/config/forces.config.js +239 -0
- package/dist/config/forces.config.js.map +1 -0
- package/dist/config/manual.d.ts +134 -0
- package/dist/config/manual.d.ts.map +1 -0
- package/dist/config/manual.js +604 -0
- package/dist/config/manual.js.map +1 -0
- package/dist/config/palettes.d.ts +18 -0
- package/dist/config/palettes.d.ts.map +1 -0
- package/dist/config/palettes.js +34 -0
- package/dist/config/palettes.js.map +1 -0
- package/dist/config/presets.d.ts +48 -0
- package/dist/config/presets.d.ts.map +1 -0
- package/dist/config/presets.js +87 -0
- package/dist/config/presets.js.map +1 -0
- package/dist/config/tokens.d.ts +3 -0
- package/dist/config/tokens.d.ts.map +1 -0
- package/dist/config/tokens.js +16 -0
- package/dist/config/tokens.js.map +1 -0
- package/dist/conformance/expectations.d.ts +40 -0
- package/dist/conformance/expectations.d.ts.map +1 -0
- package/dist/conformance/expectations.js +347 -0
- package/dist/conformance/expectations.js.map +1 -0
- package/dist/conformance/experiments.d.ts +17 -0
- package/dist/conformance/experiments.d.ts.map +1 -0
- package/dist/conformance/experiments.js +875 -0
- package/dist/conformance/experiments.js.map +1 -0
- package/dist/conformance/run.d.ts +18 -0
- package/dist/conformance/run.d.ts.map +1 -0
- package/dist/conformance/run.js +240 -0
- package/dist/conformance/run.js.map +1 -0
- package/dist/conformance/types.d.ts +100 -0
- package/dist/conformance/types.d.ts.map +1 -0
- package/dist/conformance/types.js +2 -0
- package/dist/conformance/types.js.map +1 -0
- package/dist/contracts/guards.d.ts +51 -0
- package/dist/contracts/guards.d.ts.map +1 -0
- package/dist/contracts/guards.js +100 -0
- package/dist/contracts/guards.js.map +1 -0
- package/dist/contracts/index.d.ts +18 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +107 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/passport.d.ts +88 -0
- package/dist/contracts/passport.d.ts.map +1 -0
- package/dist/contracts/passport.js +135 -0
- package/dist/contracts/passport.js.map +1 -0
- package/dist/contracts/types.d.ts +120 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +24 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/core/accretion.d.ts +50 -0
- package/dist/core/accretion.d.ts.map +1 -0
- package/dist/core/accretion.js +98 -0
- package/dist/core/accretion.js.map +1 -0
- package/dist/core/agents.d.ts +31 -0
- package/dist/core/agents.d.ts.map +1 -0
- package/dist/core/agents.js +51 -0
- package/dist/core/agents.js.map +1 -0
- package/dist/core/attention.d.ts +72 -0
- package/dist/core/attention.d.ts.map +1 -0
- package/dist/core/attention.js +122 -0
- package/dist/core/attention.js.map +1 -0
- package/dist/core/causality.d.ts +38 -0
- package/dist/core/causality.d.ts.map +1 -0
- package/dist/core/causality.js +64 -0
- package/dist/core/causality.js.map +1 -0
- package/dist/core/conditions.d.ts +10 -0
- package/dist/core/conditions.d.ts.map +1 -0
- package/dist/core/conditions.js +22 -0
- package/dist/core/conditions.js.map +1 -0
- package/dist/core/currents.d.ts +53 -0
- package/dist/core/currents.d.ts.map +1 -0
- package/dist/core/currents.js +65 -0
- package/dist/core/currents.js.map +1 -0
- package/dist/core/dock.d.ts +35 -0
- package/dist/core/dock.d.ts.map +1 -0
- package/dist/core/dock.js +39 -0
- package/dist/core/dock.js.map +1 -0
- package/dist/core/events.d.ts +23 -0
- package/dist/core/events.d.ts.map +1 -0
- package/dist/core/events.js +34 -0
- package/dist/core/events.js.map +1 -0
- package/dist/core/feedback-sink.d.ts +32 -0
- package/dist/core/feedback-sink.d.ts.map +1 -0
- package/dist/core/feedback-sink.js +53 -0
- package/dist/core/feedback-sink.js.map +1 -0
- package/dist/core/feedback.d.ts +11 -0
- package/dist/core/feedback.d.ts.map +1 -0
- package/dist/core/feedback.js +16 -0
- package/dist/core/feedback.js.map +1 -0
- package/dist/core/field-store.d.ts +26 -0
- package/dist/core/field-store.d.ts.map +1 -0
- package/dist/core/field-store.js +54 -0
- package/dist/core/field-store.js.map +1 -0
- package/dist/core/field.d.ts +18 -0
- package/dist/core/field.d.ts.map +1 -0
- package/dist/core/field.js +1943 -0
- package/dist/core/field.js.map +1 -0
- package/dist/core/fieldline-seeds.d.ts +25 -0
- package/dist/core/fieldline-seeds.d.ts.map +1 -0
- package/dist/core/fieldline-seeds.js +32 -0
- package/dist/core/fieldline-seeds.js.map +1 -0
- package/dist/core/fieldlines.d.ts +75 -0
- package/dist/core/fieldlines.d.ts.map +1 -0
- package/dist/core/fieldlines.js +111 -0
- package/dist/core/fieldlines.js.map +1 -0
- package/dist/core/flow.d.ts +38 -0
- package/dist/core/flow.d.ts.map +1 -0
- package/dist/core/flow.js +27 -0
- package/dist/core/flow.js.map +1 -0
- package/dist/core/formations.d.ts +11 -0
- package/dist/core/formations.d.ts.map +1 -0
- package/dist/core/formations.js +22 -0
- package/dist/core/formations.js.map +1 -0
- package/dist/core/geometry.d.ts +67 -0
- package/dist/core/geometry.d.ts.map +1 -0
- package/dist/core/geometry.js +68 -0
- package/dist/core/geometry.js.map +1 -0
- package/dist/core/heatmap.d.ts +22 -0
- package/dist/core/heatmap.d.ts.map +1 -0
- package/dist/core/heatmap.js +55 -0
- package/dist/core/heatmap.js.map +1 -0
- package/dist/core/host.d.ts +46 -0
- package/dist/core/host.d.ts.map +1 -0
- package/dist/core/host.js +11 -0
- package/dist/core/host.js.map +1 -0
- package/dist/core/integrator.d.ts +24 -0
- package/dist/core/integrator.d.ts.map +1 -0
- package/dist/core/integrator.js +375 -0
- package/dist/core/integrator.js.map +1 -0
- package/dist/core/math.d.ts +37 -0
- package/dist/core/math.d.ts.map +1 -0
- package/dist/core/math.js +77 -0
- package/dist/core/math.js.map +1 -0
- package/dist/core/reactions.d.ts +32 -0
- package/dist/core/reactions.d.ts.map +1 -0
- package/dist/core/reactions.js +45 -0
- package/dist/core/reactions.js.map +1 -0
- package/dist/core/registry.d.ts +13 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +20 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/render-backend.d.ts +46 -0
- package/dist/core/render-backend.d.ts.map +1 -0
- package/dist/core/render-backend.js +75 -0
- package/dist/core/render-backend.js.map +1 -0
- package/dist/core/render-modes.d.ts +42 -0
- package/dist/core/render-modes.d.ts.map +1 -0
- package/dist/core/render-modes.js +141 -0
- package/dist/core/render-modes.js.map +1 -0
- package/dist/core/reservoir.d.ts +43 -0
- package/dist/core/reservoir.d.ts.map +1 -0
- package/dist/core/reservoir.js +207 -0
- package/dist/core/reservoir.js.map +1 -0
- package/dist/core/scalar-grid.d.ts +51 -0
- package/dist/core/scalar-grid.d.ts.map +1 -0
- package/dist/core/scalar-grid.js +146 -0
- package/dist/core/scalar-grid.js.map +1 -0
- package/dist/core/scanner.d.ts +59 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +260 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/shadow.d.ts +69 -0
- package/dist/core/shadow.d.ts.map +1 -0
- package/dist/core/shadow.js +84 -0
- package/dist/core/shadow.js.map +1 -0
- package/dist/core/spatial-hash.d.ts +30 -0
- package/dist/core/spatial-hash.d.ts.map +1 -0
- package/dist/core/spatial-hash.js +64 -0
- package/dist/core/spatial-hash.js.map +1 -0
- package/dist/core/streamlines.d.ts +29 -0
- package/dist/core/streamlines.d.ts.map +1 -0
- package/dist/core/streamlines.js +70 -0
- package/dist/core/streamlines.js.map +1 -0
- package/dist/core/surface.d.ts +19 -0
- package/dist/core/surface.d.ts.map +1 -0
- package/dist/core/surface.js +21 -0
- package/dist/core/surface.js.map +1 -0
- package/dist/core/temporal.d.ts +110 -0
- package/dist/core/temporal.d.ts.map +1 -0
- package/dist/core/temporal.js +139 -0
- package/dist/core/temporal.js.map +1 -0
- package/dist/core/thermo.d.ts +48 -0
- package/dist/core/thermo.d.ts.map +1 -0
- package/dist/core/thermo.js +48 -0
- package/dist/core/thermo.js.map +1 -0
- package/dist/core/types.d.ts +610 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/weights.d.ts +111 -0
- package/dist/core/weights.d.ts.map +1 -0
- package/dist/core/weights.js +128 -0
- package/dist/core/weights.js.map +1 -0
- package/dist/diagnostics/energy.d.ts +21 -0
- package/dist/diagnostics/energy.d.ts.map +1 -0
- package/dist/diagnostics/energy.js +27 -0
- package/dist/diagnostics/energy.js.map +1 -0
- package/dist/diagnostics/fields.d.ts +23 -0
- package/dist/diagnostics/fields.d.ts.map +1 -0
- package/dist/diagnostics/fields.js +30 -0
- package/dist/diagnostics/fields.js.map +1 -0
- package/dist/diagnostics/index.d.ts +46 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +23 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/modes.d.ts +108 -0
- package/dist/diagnostics/modes.d.ts.map +1 -0
- package/dist/diagnostics/modes.js +181 -0
- package/dist/diagnostics/modes.js.map +1 -0
- package/dist/diagnostics/potential.d.ts +30 -0
- package/dist/diagnostics/potential.d.ts.map +1 -0
- package/dist/diagnostics/potential.js +43 -0
- package/dist/diagnostics/potential.js.map +1 -0
- package/dist/diagnostics/probes.d.ts +31 -0
- package/dist/diagnostics/probes.d.ts.map +1 -0
- package/dist/diagnostics/probes.js +61 -0
- package/dist/diagnostics/probes.js.map +1 -0
- package/dist/diagnostics/render.d.ts +49 -0
- package/dist/diagnostics/render.d.ts.map +1 -0
- package/dist/diagnostics/render.js +132 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/export.d.ts +18 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +17 -0
- package/dist/export.js.map +1 -0
- package/dist/forces/extended.d.ts +121 -0
- package/dist/forces/extended.d.ts.map +1 -0
- package/dist/forces/extended.js +674 -0
- package/dist/forces/extended.js.map +1 -0
- package/dist/forces/index.d.ts +33 -0
- package/dist/forces/index.d.ts.map +1 -0
- package/dist/forces/index.js +237 -0
- package/dist/forces/index.js.map +1 -0
- package/dist/forces/natural.d.ts +106 -0
- package/dist/forces/natural.d.ts.map +1 -0
- package/dist/forces/natural.js +385 -0
- package/dist/forces/natural.js.map +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/budget.d.ts +17 -0
- package/dist/inspect/budget.d.ts.map +1 -0
- package/dist/inspect/budget.js +19 -0
- package/dist/inspect/budget.js.map +1 -0
- package/dist/inspect/index.d.ts +10 -0
- package/dist/inspect/index.d.ts.map +1 -0
- package/dist/inspect/index.js +10 -0
- package/dist/inspect/index.js.map +1 -0
- package/dist/inspect/report.d.ts +17 -0
- package/dist/inspect/report.d.ts.map +1 -0
- package/dist/inspect/report.js +44 -0
- package/dist/inspect/report.js.map +1 -0
- package/dist/inspect/snapshot.d.ts +21 -0
- package/dist/inspect/snapshot.d.ts.map +1 -0
- package/dist/inspect/snapshot.js +30 -0
- package/dist/inspect/snapshot.js.map +1 -0
- package/dist/recipes/catalog.d.ts +51 -0
- package/dist/recipes/catalog.d.ts.map +1 -0
- package/dist/recipes/catalog.js +1496 -0
- package/dist/recipes/catalog.js.map +1 -0
- package/dist/recipes/charge.d.ts +18 -0
- package/dist/recipes/charge.d.ts.map +1 -0
- package/dist/recipes/charge.js +27 -0
- package/dist/recipes/charge.js.map +1 -0
- package/dist/recipes/compile.d.ts +93 -0
- package/dist/recipes/compile.d.ts.map +1 -0
- package/dist/recipes/compile.js +113 -0
- package/dist/recipes/compile.js.map +1 -0
- package/dist/recipes/explain.d.ts +8 -0
- package/dist/recipes/explain.d.ts.map +1 -0
- package/dist/recipes/explain.js +46 -0
- package/dist/recipes/explain.js.map +1 -0
- package/dist/recipes/gallery.d.ts +6 -0
- package/dist/recipes/gallery.d.ts.map +1 -0
- package/dist/recipes/gallery.js +6 -0
- package/dist/recipes/gallery.js.map +1 -0
- package/dist/recipes/gravity.d.ts +16 -0
- package/dist/recipes/gravity.d.ts.map +1 -0
- package/dist/recipes/gravity.js +27 -0
- package/dist/recipes/gravity.js.map +1 -0
- package/dist/recipes/index.d.ts +18 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +36 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/recipes/intent.d.ts +44 -0
- package/dist/recipes/intent.d.ts.map +1 -0
- package/dist/recipes/intent.js +46 -0
- package/dist/recipes/intent.js.map +1 -0
- package/dist/recipes/schema.d.ts +103 -0
- package/dist/recipes/schema.d.ts.map +1 -0
- package/dist/recipes/schema.js +123 -0
- package/dist/recipes/schema.js.map +1 -0
- package/dist/recipes/wayfinding.d.ts +39 -0
- package/dist/recipes/wayfinding.d.ts.map +1 -0
- package/dist/recipes/wayfinding.js +77 -0
- package/dist/recipes/wayfinding.js.map +1 -0
- package/dist/semantic/index.d.ts +13 -0
- package/dist/semantic/index.d.ts.map +1 -0
- package/dist/semantic/index.js +31 -0
- package/dist/semantic/index.js.map +1 -0
- package/dist/semantic/layers.d.ts +24 -0
- package/dist/semantic/layers.d.ts.map +1 -0
- package/dist/semantic/layers.js +27 -0
- package/dist/semantic/layers.js.map +1 -0
- package/dist/semantic/materials.d.ts +20 -0
- package/dist/semantic/materials.d.ts.map +1 -0
- package/dist/semantic/materials.js +17 -0
- package/dist/semantic/materials.js.map +1 -0
- package/dist/semantic/states.d.ts +11 -0
- package/dist/semantic/states.d.ts.map +1 -0
- package/dist/semantic/states.js +26 -0
- package/dist/semantic/states.js.map +1 -0
- package/dist/visual/channels.d.ts +71 -0
- package/dist/visual/channels.d.ts.map +1 -0
- package/dist/visual/channels.js +70 -0
- package/dist/visual/channels.js.map +1 -0
- package/dist/visual/index.d.ts +39 -0
- package/dist/visual/index.d.ts.map +1 -0
- package/dist/visual/index.js +30 -0
- package/dist/visual/index.js.map +1 -0
- package/dist/visual/lint.d.ts +41 -0
- package/dist/visual/lint.d.ts.map +1 -0
- package/dist/visual/lint.js +58 -0
- package/dist/visual/lint.js.map +1 -0
- package/dist/visual/mapping.d.ts +13 -0
- package/dist/visual/mapping.d.ts.map +1 -0
- package/dist/visual/mapping.js +43 -0
- package/dist/visual/mapping.js.map +1 -0
- package/dist/visual/semantic-text.d.ts +28 -0
- package/dist/visual/semantic-text.d.ts.map +1 -0
- package/dist/visual/semantic-text.js +36 -0
- package/dist/visual/semantic-text.js.map +1 -0
- package/dist/visual/tokens.d.ts +23 -0
- package/dist/visual/tokens.d.ts.map +1 -0
- package/dist/visual/tokens.js +54 -0
- package/dist/visual/tokens.js.map +1 -0
- package/dist/visual/visualization.d.ts +31 -0
- package/dist/visual/visualization.d.ts.map +1 -0
- package/dist/visual/visualization.js +47 -0
- package/dist/visual/visualization.js.map +1 -0
- 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"}
|