import type { RepresentationHitN, RepresentationSourceN } from './types.js'; import type { FieldEvaluation4 } from '../field/types.js'; /** * How many distinct sources one observation names. * * This is a different question from {@link RepresentationAmbiguity}, which says * whether a hit names more than one source *point* under the display map. A * manipulation does not act on a point; it acts on a source. The two are * independent, and all four combinations occur: * * | display map | coincident sources | point ambiguity | target multiplicity | * | --- | --- | --- | --- | * | injective section | one | `none` | `unique` | * | lossy projection | one | `projection-overlap` | `unique` | * | injective section | two | `none` | `multiple` | * | lossy projection | two | `projection-overlap` | `multiple` | * * The third row is the one that costs a caller something: the point really is * unique, so `ambiguity: 'none'` is truthful, and two source cells are still * candidates for the action. */ export type RepresentationTargetMultiplicity = 'none' | 'unique' | 'multiple'; /** * One manipulation target, and every supplied hit that named it. * * `hits` keeps all of them, including hits that disagree about * `ambientPointStatus` or land on different rendered primitives: those are * different observations *of one source*, not different sources. */ export interface RepresentationCandidateN { /** The source this candidate names, taken from its first supplied hit. */ readonly source: RepresentationSourceN; /** Every supplied hit that resolved to this source, in encounter order. */ readonly hits: readonly RepresentationHitN[]; /** How many hits were grouped here. Never a primitive or intersection count. */ readonly hitCount: number; } /** * The observation was empty: no hits were supplied at all. * * This arm means exactly that, and `hitCount === 0` is true by construction. A * hit whose source cannot be identified never lands here — it throws, so a * partially-understood observation can never be narrowed to a target. */ export interface NoRepresentationCandidatesN { /** Narrows a {@link RepresentationCandidateSetN} to the empty observation. */ readonly targetMultiplicity: 'none'; /** Empty, so a caller iterating candidates needs no special case here. */ readonly candidates: readonly []; /** How many hits were supplied. Zero here by construction. */ readonly hitCount: 0; /** How many distinct sources were named. Zero here by construction. */ readonly candidateCount: 0; } /** Exactly one source was named; a caller may act on it without asking. */ export interface UniqueRepresentationCandidateN { /** Narrows a {@link RepresentationCandidateSetN} to a single named source. */ readonly targetMultiplicity: 'unique'; /** The one source named, reachable without indexing into a list. */ readonly candidate: RepresentationCandidateN; /** The same candidate as a one-element list, for uniform iteration. */ readonly candidates: readonly [RepresentationCandidateN]; /** Total hits supplied, which may exceed one for a single source. */ readonly hitCount: number; /** How many distinct sources were named. One here by construction. */ readonly candidateCount: 1; } /** Several sources were named; acting on one of them is the caller's choice. */ export interface MultipleRepresentationCandidatesN { /** Narrows a {@link RepresentationCandidateSetN} to a contested observation. */ readonly targetMultiplicity: 'multiple'; /** Every named source, in the order its first hit arrived — and only that. */ readonly candidates: readonly RepresentationCandidateN[]; /** Total hits supplied across every candidate. */ readonly hitCount: number; /** How many distinct sources were named; at least two here. */ readonly candidateCount: number; } /** What one observation names, discriminated on how many sources it found. */ export type RepresentationCandidateSetN = NoRepresentationCandidatesN | UniqueRepresentationCandidateN | MultipleRepresentationCandidatesN; /** * Groups the hits of one visual observation into distinct manipulation targets. * * Hand it every {@link RepresentationHitN} produced for one observation — one * per renderer intersection, adapted by whichever released adapter matches the * product each intersection came from — and it reports whether that observation * names zero, one, or several sources. * * Grouping is by **live object identity**: for a cell, its complex object, its * group object, and its ordinal within that group. Two distinct complexes stay * two candidates even when their `SourceCellIdN` values are byte-identical, * which they are whenever two sources were authored the same way. * * Encounter order is preserved and means only that: the order the hits arrived * in. It is not nearest, preferred, authoritative, or a priority. * * **Fail-closed.** Every hit must resolve to an identity or the call throws. * There is no arm in which an unclassified hit sits beside a target a caller is * told is safe to act on, and `hitCount` always equals the sum of the * candidates' own counts. `'none'` means the input was empty, not that nothing * could be understood. * * **Retired topology is refused.** A cell's `SourceCellReferenceN` is inspected * before it may name a candidate; a retired one throws with its reason. This is * a topology check the reference already supports, not a freshness check: a * hit's pose and ambient point may still be out of date and nothing here * detects that. * * **Structural immutability.** The result, its candidates array, each candidate * record and each candidate's hits array are frozen, so the grouping's own * structure and counts cannot drift. The hits, sources, complexes, fields and * evaluation records reached through them are caller-owned and are left exactly * as they arrived. * * **What this does not do.** It groups the hits it is given and nothing else. * It cannot discover an intersection a renderer or caller left out; it does not * map a renderer intersection back to the product that drew it; it does not * decide which candidate is nearest or preferred; it does not detect whether a * hit's pose or ambient point has since gone stale; it keeps no clock, version, * registry or persistent state; and it does not make `SourceCellIdN` globally * unique. It also never modifies or reinterprets a hit's point-level * `ambiguity`, which continues to answer its own separate question. * * @param hits - Every hit produced for one observation, in encounter order. * * @example * ```ts * // Pass every hit for one observation — one per renderer intersection, * // each adapted first. Taking `intersections[0]` discards the rest. * const grouped = groupRepresentationCandidatesN([]); * if (grouped.targetMultiplicity === 'unique') { * grouped.candidate.source; // one source named: safe to act on * } else if (grouped.targetMultiplicity === 'multiple') { * grouped.candidates.length; // several named: the caller chooses * } * // Point-level ambiguity is a separate reading, and is left untouched. * grouped.candidates[0]?.hits[0]?.ambiguity; * ``` */ export declare function groupRepresentationCandidatesN(hits: readonly RepresentationHitN[]): RepresentationCandidateSetN; //# sourceMappingURL=candidates.d.ts.map