import { type SourceSimplexReferenceN } from '@holotope/core'; import { type XpbdIncrementalPotentialStepFilterN } from './xpbd-incremental-potential-step-filter.js'; import { XpbdParticleBindingN } from './xpbd-particle-binding.js'; import { type XpbdConservativeForceProviderN } from './xpbd-world.js'; /** * Publication failures of the exact point--simplex query, forwarded one to one. * * Flattening them would lose the distinction the query already draws: a weight * that underflowed, a value that overflowed, a value that underflowed, and an * accuracy bound that overflowed are four different representation failures * with four different repairs. None of them classifies recoverability — see * the note on the union below. */ export type XpbdSourceSimplexMeasureBarrierPublicationReasonN = 'point-simplex-weight-underflow' | 'point-simplex-value-overflow' | 'point-simplex-value-underflow' | 'point-simplex-accuracy-bound-overflow'; /** * Open-domain refusal vocabulary of the measure-weighted normal-contact law. * * Every reason here is a statement about ONE quadrature node at ONE candidate * placement, except `accumulated-value-outside-float64`, which is a statement * about the reduction of all of them. The law refuses as a whole: a single * node that cannot be evaluated leaves the sum undefined, so there is no * partial energy to publish and none is published. * * A reason does NOT classify recoverability. Whether a shorter step clears the * refusal is a property of the iterate the search is standing on, not of the * name: if the offending node's query publishes at the start of the segment, * the failure arrived somewhere along it and contraction can retreat out of * it; if it does not publish there, every contracted trial converges back onto * the placement that already fails. */ export type XpbdSourceSimplexMeasureBarrierDomainReasonN = 'at-or-below-minimum-distance' | 'minimum-distance-not-certified' | 'zero-or-intersecting' | 'obstacle-rank-deficient' | XpbdSourceSimplexMeasureBarrierPublicationReasonN | 'direction-error-exceeds-policy' | 'barrier-component-outside-float64' | 'accumulated-value-outside-float64'; /** * Authored inputs for one measure-weighted normal-contact term. * * There is no `dimension`: the ambient dimension is read off the source * complexes and the binding, which already agree or the construction fails. * There is no quadrature control either — see `compileXpbdSourceSimplexMeasure\ * BarrierN` for why the rule is fixed. */ export interface CompileXpbdSourceSimplexMeasureBarrierNOptions { /** Stable term identity; also the `lawId` of every domain refusal it raises. */ readonly id: string; /** Authoritative source-vertex to particle mapping for the deforming cell. */ readonly binding: XpbdParticleBindingN; /** The deforming source simplex, whose measure weights the energy. */ readonly cell: SourceSimplexReferenceN; /** The opposing source simplex; refused by name when retired. */ readonly obstacle: SourceSimplexReferenceN; /** * Binding for a MOVING obstacle. Every particle it contributes must be * kinematic (`inverseMass === 0`); omit it for a static obstacle read from * the source complex. */ readonly obstacleBinding?: XpbdParticleBindingN; /** Distance at and above which every node's energy is exactly zero. */ readonly activationDistance: number; /** Positive energy scale, per unit of reference measure. */ readonly stiffness: number; /** * Largest published direction-enclosure radius this term will act on. There * is no default: the exact query publishes an enclosure, and no universal * value exists, so the policy is authored or the construction fails. */ readonly maximumDirectionError: number; /** Open unsigned distance boundary shared by every node. Default zero. */ readonly minimumDistance?: number; /** Fraction of the certified prefix the filter retains. Default `0.9`. */ readonly conservativeScale?: number; } /** The compiled term: one conservative provider and one paired step filter. */ export interface XpbdSourceSimplexMeasureBarrierTermsN { /** Register with `world.addForceProvider` or a compiled potential problem. */ readonly provider: XpbdConservativeForceProviderN; /** Register alongside it; the two are only sound together. */ readonly stepFilter: XpbdIncrementalPotentialStepFilterN; } /** * Compiles one measure-weighted normal-contact term for the released world. * * The energy is the cell's reference k-measure times the fixed-rule average of * a clamped logarithmic barrier on each node's exact distance to the obstacle. * It is a **measure-consistent fixed quadrature**: what it buys over a * per-vertex barrier is that the contact resists by the SIZE of the touching * feature rather than by the number of vertices describing it, so splitting a * cell in two does not answer twice. * * ## What that does and does not mean under subdivision * * Removing direct cell-count multiplication is not the same as being invariant * under subdivision, and the two must not be conflated: * * - **Constant integrand** — when the barrier is constant over the cell, as it * is for a cell parallel to a flat obstacle, subdivision is exactly additive * up to Float64 reduction. * - **General nonconstant integrand** — subdivision moves the sample * locations, so a fixed finite rule normally returns a different estimate. * Measured on two legal refinements of the same source region: a tilted cell * split in half changes by about 27%, and an uneven split of a curved * arrangement by about 44%. * - **Convergence** — the refinement sequence approaches the continuum * integral. Measured against an independent composite Gauss--Legendre * reference built from the released query and barrier, the error falls at * second order in the cell size on that fixture, with the single-cell * estimate about 28% below the continuum value. That is a measurement on a * named fixture; **no truncation bound is proved and none is claimed**. * * Supported source dimensions are `k = 1, 2, 3`, the range over which the * exact point--simplex query publishes a direction enclosure. * * The quadrature is fixed and not authorable. A caller-supplied rule would * make the energy a function of the rule, and every stated property here — * that the forces are its exact gradient, that the filter's Lipschitz bound * covers every node, that the term refuses as a whole — is a property of THIS * rule. Offering a knob would offer those guarantees on rules that have not * been measured. It is **not authorable through the public API** either: no * rule option is accepted and no rule property exists to overwrite. * * ## The privacy boundary, stated here in full * * These three statements travel with this declaration — into the emitted * JavaScript and into the packed `.d.ts` — rather than pointing at a note on * an internal function, which a declaration file does not carry. * * 1. **No private rule, snapshot or numerical policy is exposed as a public * property, option or API.** There is no rule option to pass, and no rule, * reference measure, obstacle snapshot or conservative scale property to * overwrite. * 2. **Same-realm metaprogramming may observe frozen otherwise-private objects * and ephemeral per-call objects** — numeric accessors installed on * `Array.prototype` before compilation retain the static-obstacle snapshot, * the fixed rule and a private particle partition, and a replaced inherited * operation receives whatever is used as its receiver. A retained * otherwise-private object **cannot be modified after the intrinsic is * restored to affect a later clean evaluation**: those arrays are frozen, * and the per-call geometry is freshly allocated. This is a consequence * boundary, not concealment. * 3. **The published `particles` are intentionally public, caller-owned live * inputs and are excluded from that guarantee.** Moving them changes later * evaluations, which is what a contact term reading live state is for. * * A successful evaluation carries exactly `potentialEnergy` and `forces`. * There is no inspection surface, and the companion `stepFilter` is required * rather than optional — without it, nothing prevents a step from leaping * through the obstacle, because the law measures unsigned distance and has no * notion of side. * * @example * A strip above a floor triangle at CONSTANT distance, and the same strip * subdivided. Here — and only because the sampled barrier is constant along * the cell — subdivision is exactly additive. Tilt the strip and the two * answers differ, because a fixed finite rule samples different places. * ```ts * const floor = new CellComplex(3, Float64Array.from([ * -40, 0, -40, 60, 0, -40, -40, 0, 60 * ]), [{ dim: 2, verticesPerCell: 3, kind: 'simplex', * indices: Uint32Array.from([0, 1, 2]) }]); * const floorGroup = floor.groups[0]; * if (floorGroup === undefined) throw new Error('expected the floor group'); * const obstacle = createSourceSimplexReferenceN( * createSourceCellReferenceN(floor, floorGroup, 0) * ); * * const energies = [[0, 1], [0, 0.5], [0.5, 1]].map(([from, to]) => { * const strip = new CellComplex(3, Float64Array.from([ * from, 0.5, 0, to, 0.5, 0 * ]), [{ dim: 1, verticesPerCell: 2, kind: 'simplex', * indices: Uint32Array.from([0, 1]) }]); * const stripGroup = strip.groups[0]; * if (stripGroup === undefined) throw new Error('expected the strip group'); * const terms = compileXpbdSourceSimplexMeasureBarrierN({ * id: `contact-${from}`, * binding: compileXpbdParticleBindingN({ * id: `strip-${from}`, source: strip * }), * cell: createSourceSimplexReferenceN( * createSourceCellReferenceN(strip, stripGroup, 0) * ), * obstacle, * minimumDistance: 0.05, * activationDistance: 1, * stiffness: 2, * maximumDirectionError: 1e-6 * }); * return terms.provider.evaluate().potentialEnergy; * }); * const [whole, left, right] = energies; * if (whole === undefined || left === undefined || right === undefined) { * throw new Error('expected three energies'); * } * * log('one cell ', whole); * log('two cells', left + right); // the same number, for a CONSTANT barrier * ``` * * @param options Term identity, geometry, and barrier policy. * @returns The provider and the step filter that are only sound together. * @throws Error For any authored input outside the declared domain — including * a rest-degenerate cell, a rank-deficient static obstacle, and an obstacle * binding contributing a particle that is not kinematic. These are * configuration errors and no candidate retry can fix them. */ export declare function compileXpbdSourceSimplexMeasureBarrierN(options: CompileXpbdSourceSimplexMeasureBarrierNOptions): XpbdSourceSimplexMeasureBarrierTermsN; //# sourceMappingURL=xpbd-source-simplex-measure-barrier.d.ts.map