import type { VecN } from '@holotope/core'; import { type XpbdIncrementalPotentialDiagnosisN } from './xpbd-incremental-potential-diagnosis.js'; import { type XpbdIncrementalPotentialApplicationPolicyN, type XpbdIncrementalPotentialMinimizationPolicyN, type XpbdIncrementalPotentialStepResultN } from './xpbd-incremental-potential-step.js'; import type { XpbdIncrementalPotentialFeasibleWarmStartNOptions } from './xpbd-incremental-potential-feasible-base.js'; import type { XpbdIncrementalPotentialStepFilterN } from './xpbd-incremental-potential-step-filter.js'; import { XpbdWorldN, type XpbdConservativeForceProviderN } from './xpbd-world.js'; /** * One nonlinear advance of an authored world through the incremental-potential * transaction. * * The world is authoritative for dimension, particle order, gravity, and the * force-provider registry, so a caller stops repeating those four at every * step and they cannot drift from the world being rendered or inspected. * * Step filters stay explicit. `XpbdWorldN` owns no filter registry, and * inventing an implicit global one would make an ordered policy invisible at * the call site. */ export interface StepXpbdIncrementalPotentialWorldNOptions { /** Authoritative dimension, particle order, gravity, and provider registry. */ readonly world: XpbdWorldN; /** Ordered admissible-segment filters; no implicit world registry exists. */ readonly stepFilters?: readonly XpbdIncrementalPotentialStepFilterN[]; /** * Transient conservative providers prepared for **this step only**, such as * a lagged friction term frozen at the accepted base state. * * They are deliberately not part of the world's authored registry: the world * stays authoritative for what a scene *is*, while these describe what one * transaction additionally minimizes against. They appear in the returned * selection evidence under their own field, never merged into * `providerIds`, so an authored base term and a transient lagged one are * always distinguishable after the fact. Supplying none leaves this step's * behaviour and its returned object exactly as they were. */ readonly preparedProviders?: readonly XpbdConservativeForceProviderN[]; /** * Physical interval, finite and strictly positive. * * A zero interval is not a cheap no-op step: it is not a physical * optimization step at all, and it is rejected exactly as negative and * non-finite intervals are. A render loop that can produce a zero elapsed * time should skip that frame rather than ask for a step of it. */ readonly deltaTime: number; /** * Explicit minimizer base in the world's particle order. * * Takes precedence over `warmStart` and bypasses both feasible-base * recovery and the warm-start segment certification entirely, so a step * given one returns no `feasibleBaseRecovery` and no * `warmStartCertification` evidence. The coordinates are the caller's own * uncertified decision: no registered filter is consulted about the * movement to them. */ readonly initialPositions?: readonly VecN[]; /** Minimizer base when `initialPositions` is absent; see the lower step. */ readonly warmStart?: 'inertial-prediction' | 'previous-positions' | 'feasible-inertial-prediction'; /** * Chord-sampling controls belonging to `feasible-inertial-prediction`. * Supplying them with another warm start is rejected, not ignored. */ readonly feasibleWarmStart?: XpbdIncrementalPotentialFeasibleWarmStartNOptions; /** Direction policy, tolerances, and budgets for the bounded minimizer. */ readonly minimization?: XpbdIncrementalPotentialMinimizationPolicyN; /** Velocity-reconstruction and force-clearing policy for a converged step. */ readonly application?: XpbdIncrementalPotentialApplicationPolicyN; } /** * What the world contributed, captured before delegation. * * Identifiers rather than live objects: this is evidence about which registry * entries the step ran against, and it must stay readable after the particles * it names have moved. */ export interface XpbdIncrementalPotentialWorldSelectionN { /** Ambient dimension read from the world, not from the options. */ readonly dimension: number; /** Every registered particle, in world registration order. */ readonly particleIds: readonly string[]; /** Every registered force provider, in authored world order. */ readonly providerIds: readonly string[]; /** The supplied filters, in supplied order; never sorted or deduplicated. */ readonly stepFilterIds: readonly string[]; /** * Transient prepared providers, in supplied order — present **only** when * some were supplied, so a step without them returns the object it always * did. Kept separate from `providerIds` on purpose: a reader must be able to * tell an authored scene term from a one-transaction lagged one. */ readonly preparedProviderIds?: readonly string[]; } /** * The complete result of one world-scoped advance. * * `step` is the unchanged lower-level result, not a simplified success flag — * every feasible-base trial, candidate record, Newton evidence, minimizer * terminal, and application refusal remains reachable through it. */ export interface XpbdIncrementalPotentialWorldStepN { /** Which registry entries this step ran against, captured before it ran. */ readonly selection: XpbdIncrementalPotentialWorldSelectionN; /** The unchanged lower-level transaction result, refusals included. */ readonly step: XpbdIncrementalPotentialStepResultN; /** That same result classified once, so no caller parses a message. */ readonly diagnosis: XpbdIncrementalPotentialDiagnosisN; } /** * Advances one authored `XpbdWorldN` through the incremental-potential * transaction, once. * * This is orchestration, not a second solver. It derives the four * authoritative inputs from the world, refuses the registries the optimization * path cannot honor, delegates exactly once to * {@link stepXpbdIncrementalPotentialN}, and diagnoses that result once. Every * numeric decision — prediction, compilation, feasible-base recovery, * minimization, verification, application, rollback — belongs to the delegated * step and is unchanged here. * * A world has two solver paths and they are not interchangeable for one * physical interval. `world.step()` and `world.stepAdaptive()` run projected * XPBD with the registered constraints, responses, and guards; this runs the * nonlinear optimization transaction with conservative potentials only. * Choose one per interval. This function never calls either of the others. * * Configuration problems throw, because no physical step was attempted: * unknown option keys, an empty world, an unsupported registry, a * non-conservative provider. Mathematical and algorithmic outcomes stay typed * in the returned `step`, including every refusal — they are results, not * errors. * * @param options - The world, an explicit filter order, the interval, and the * warm-start/minimization/application policies the lower-level step accepts. * @returns Frozen selection evidence, the unchanged lower-level step, and its * diagnosis. * @throws If the options or the world's registries cannot express a legal * optimization step. Thrown before any particle is touched. * * @example * One R4 point falling onto a finite static tetrahedron. The world supplies * dimension, particle order, gravity, and the contact provider; only the * ordered filter and the interval are named at the call site. * ```ts * const point = new CellComplex(4, Float64Array.from([0.25, 0.25, 0.25, 0.06]), []); * const binding = compileXpbdParticleBindingN({ id: 'point', source: point }); * for (const particle of binding.particles) particle.velocity.data[3] = -6; * * const obstacle = new CellComplex( * 4, * Float64Array.from([0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]), * [{ * key: 'obstacle', dim: 3, verticesPerCell: 4, kind: 'simplex', * indices: Uint32Array.from([0, 1, 2, 3]) * }] * ); * const [face] = obstacle.cellsOfDim(3); * if (!face) throw new Error('the obstacle has no 3-cells'); * * const contact = compileXpbdParticleSourceSimplexBarrierFamilyN({ * id: 'contact', binding, obstacle, simplexGroup: face, * minimumDistance: 0.05, activationDistance: 0.8, stiffness: 1.7 * }); * * const world = new XpbdWorldN({ dimension: 4, gravity: [0, 0, 0, -9.81] }); * binding.addToWorld(world); * contact.addToWorld(world); * * const advance = stepXpbdIncrementalPotentialWorldN({ * world, * deltaTime: 1 / 120, * stepFilters: [contact.stepFilter], * warmStart: 'feasible-inertial-prediction', * minimization: { directionPolicy: 'steepest-descent' } * }); * * log(advance.selection.providerIds); // ['contact'] — authored world order * log(advance.step.status); // 'applied', or a typed refusal * log(advance.diagnosis.condition); // why, without parsing a message * ``` */ export declare function stepXpbdIncrementalPotentialWorldN(options: StepXpbdIncrementalPotentialWorldNOptions): XpbdIncrementalPotentialWorldStepN; //# sourceMappingURL=xpbd-incremental-potential-world-step.d.ts.map