/**
 * Shared Narrowphase Module
 *
 * Provides contact-computing narrowphase tests and a generic collision
 * iteration pipeline used by both the collision plugin (event-only) and
 * the physics2D plugin (impulse response).
 */
import type { SpatialIndex } from './spatial-hash';
/**
 * Contact result from a narrowphase test. Normal points from A toward B.
 *
 * Narrowphase functions use this as an out-parameter: the caller owns the
 * struct, the function writes fields in place and returns `true` on hit.
 * The `onContact` callback in `detectCollisions` receives a shared module-
 * level instance — **subscribers must consume it synchronously and must not
 * retain the reference across frames**.
 */
export interface Contact {
    normalX: number;
    normalY: number;
    /** Penetration depth (positive = overlapping) */
    depth: number;
}
/** Collider shape discriminator for the flattened BaseColliderInfo layout. */
export declare const AABB_SHAPE = 0;
export declare const CIRCLE_SHAPE = 1;
export type ColliderShape = typeof AABB_SHAPE | typeof CIRCLE_SHAPE;
/**
 * Minimum collider data shared by collision and physics bundles.
 *
 * Flat layout (no nested `aabb` / `circle` sub-objects): the `shape`
 * discriminator tells you whether to read `halfWidth`/`halfHeight`
 * (AABB) or `radius` (Circle). Unused fields are set to 0.
 *
 * This shape is pool-friendly — all fields are assigned in place each
 * frame without allocating nested objects.
 */
export interface BaseColliderInfo<L extends string = string> {
    entityId: number;
    x: number;
    y: number;
    layer: L;
    collidesWith: readonly L[];
    /**
     * Bit assigned to `layer` from the lazy layer registry. Populated by
     * `fillBaseColliderInfo`. Used together with `collidesWithMask` to
     * replace per-pair `Array.includes` layer checks with a single AND.
     */
    layerBit: number;
    /** OR of `getLayerBit` for every entry in `collidesWith`. */
    collidesWithMask: number;
    shape: ColliderShape;
    halfWidth: number;
    halfHeight: number;
    radius: number;
}
export declare const getLayerBit: (layer: string) => number;
export declare const getCollidesWithMask: (collidesWith: readonly string[]) => number;
/**
 * Populate a `BaseColliderInfo` slot in place from raw component data.
 * Returns `true` if the slot was filled, `false` if the entity has no
 * collider (caller should skip it).
 *
 * If an entity has both AABB and circle colliders, AABB wins and only
 * the AABB offset is applied. This matches the dispatch precedence in
 * `computeContact`; the previous implementation stacked both offsets,
 * which was a bug.
 */
export declare function fillBaseColliderInfo<L extends string>(info: BaseColliderInfo<L>, entityId: number, x: number, y: number, layer: L, collidesWith: readonly L[], aabb: {
    width: number;
    height: number;
    offsetX?: number;
    offsetY?: number;
} | undefined, circle: {
    radius: number;
    offsetX?: number;
    offsetY?: number;
} | undefined): boolean;
/**
 * Retrieve the optional spatialIndex resource, returning undefined when absent.
 * Centralizes the cross-plugin typed lookup so individual plugins don't each
 * need to import SpatialIndex or repeat the tryGetResource pattern.
 */
export declare function tryGetSpatialIndex(tryGetResource: <T>(key: string) => T | undefined): SpatialIndex | undefined;
/**
 * Write an AABB-AABB contact into `out`. Returns `true` if the shapes
 * overlap (out was filled), `false` otherwise.
 */
export declare function computeAABBvsAABB(ax: number, ay: number, ahw: number, ahh: number, bx: number, by: number, bhw: number, bhh: number, out: Contact): boolean;
export declare function computeCircleVsCircle(ax: number, ay: number, ar: number, bx: number, by: number, br: number, out: Contact): boolean;
export declare function computeAABBvsCircle(aabbX: number, aabbY: number, ahw: number, ahh: number, circleX: number, circleY: number, radius: number, out: Contact): boolean;
/**
 * Dispatch to the correct narrowphase function for the given pair and
 * write the contact into `out`. Returns `true` if the pair overlaps.
 */
export declare function computeContact(a: BaseColliderInfo, b: BaseColliderInfo, out: Contact): boolean;
/**
 * Per-caller scratch for the broadphase entityId → collider lookup.
 *
 * Dense `arr` indexed by entityId, paired with a `gen` stamp array that marks
 * which slots are live this call. Bumping `current` invalidates all prior
 * entries without clearing — replaces the per-frame `Map.clear()` + N
 * `Map.set()` allocation churn that a `Map<number, I>` would incur.
 *
 * Owned per plugin instance (alongside its `colliderPool`), so concurrent
 * worlds don't share state and `I` stays fully typed without erasure.
 */
export interface BroadphaseScratch<I extends BaseColliderInfo> {
    arr: (I | undefined)[];
    gen: number[];
    current: number;
}
export declare function createBroadphaseScratch<I extends BaseColliderInfo>(): BroadphaseScratch<I>;
/**
 * Generic collision detection pipeline: brute-force or broadphase,
 * with layer filtering and contact computation.
 *
 * `count` is the number of live entries at the front of `colliders`.
 * The array itself may be a grow-only pool — only indices `[0, count)`
 * are iterated, so trailing pool slots are ignored.
 *
 * `scratch` is a caller-owned `BroadphaseScratch<I>` used by the broadphase
 * path as an entityId → collider lookup. Allocate it once per plugin instance
 * and pass the same reference every call.
 *
 * Uses a context parameter forwarded to the callback to avoid
 * per-frame closure allocation.
 */
export declare function detectCollisions<I extends BaseColliderInfo, C>(colliders: I[], count: number, scratch: BroadphaseScratch<I>, spatialIndex: SpatialIndex | undefined, onContact: (a: I, b: I, contact: Contact, context: C) => void, context: C): void;
