export type Cell = readonly [number, number]; export type Footprint = readonly Cell[]; export type Rotation = 0 | 1 | 2 | 3; export interface ShapedItem { id: string; value: T; footprint: Footprint; } export interface Placement { id: string; value: T; footprint: Footprint; origin: Cell; rotation: Rotation; } export interface ShapedGrid { readonly width: number; readonly height: number; readonly placements: readonly Placement[]; } export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id" | "invalid-footprint" | "invalid-origin"; export interface ShapedPlaceResult { status: "ok"; grid: ShapedGrid; } export interface ShapedRejected { status: "rejected"; reason: ShapedRejection; detail?: string; } export type ShapedResult = ShapedPlaceResult | ShapedRejected; /** @internal */ export declare function normalizeFootprint(footprint: Footprint): Footprint; /** @internal */ export declare function rotateFootprint(footprint: Footprint, rotation: Rotation): Footprint; /** @internal */ export declare function occupiedCells(footprint: Footprint, origin: Cell, rotation: Rotation): Cell[]; /** * An immutable spatial inventory board holding caller-defined multi-cell items. * * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement */ export declare function createShapedGrid(width: number, height: number): ShapedGrid; /** * Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved. * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement */ export declare function canPlace(grid: ShapedGrid, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null; /** Search policy: rotation order first, then rows and columns; the work budget counts candidate origins. */ export interface ShapedPlacementSearchOptions { /** Defaults to `[0, 1, 2, 3]`; a game can prefer or forbid rotations. */ rotations?: readonly Rotation[]; /** Ignore an existing placement while searching for its next position. */ ignoreId?: string; /** Nonnegative integer candidate limit; defaults to 4096. */ maxChecks?: number; } /** A detached placement, a completed no-fit search, or an incomplete search that reached its budget. */ export type ShapedPlacementSearchResult = { status: "found"; origin: Cell; rotation: Rotation; checks: number; } | { status: "no-space"; checks: number; } | { status: "budget-exceeded"; checks: number; }; /** * Find the first legal placement without mutating the board; occupancy is indexed once per search. * * @capability shaped-placement find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget */ export declare function findShapedPlacement(grid: ShapedGrid, footprint: Footprint, options?: ShapedPlacementSearchOptions): ShapedPlacementSearchResult; /** * Place a caller-defined item without mutating the source board; duplicate ids reject. * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement */ export declare function placeShaped(grid: ShapedGrid, item: ShapedItem, origin: Cell, rotation?: Rotation): ShapedResult; /** * Why a first-fit placement failed: the footprint cannot fit an empty board at any offered rotation * (`too-large`), the board is genuinely full (`no-space`), or the search hit its budget. */ export type ShapedFitRejection = "duplicate-id" | "invalid-footprint" | "too-large" | "no-space" | "budget-exceeded"; /** A placed board, or a refusal that names why the item does not fit. */ export type ShapedFitResult = { status: "ok"; grid: ShapedGrid; origin: Cell; rotation: Rotation; checks: number; } | { status: "rejected"; reason: ShapedFitRejection; detail: string; checks: number; }; /** * Place a shaped item at the first legal spot, or say why it does not fit. Distinguishes an item that * can never fit the board (`too-large`, with its footprint and the board size in `detail`) from a full * board (`no-space`) and an exhausted search (`budget-exceeded`, not a full bag). * * @capability shaped-placement place a shaped item at its first fit and name why it does not fit */ export declare function fitShapedItem(grid: ShapedGrid, item: ShapedItem, options?: ShapedPlacementSearchOptions): ShapedFitResult; /** * Remove one placement without mutating the source board. * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement */ export declare function removeShaped(grid: ShapedGrid, id: string): ShapedResult; /** * Move or rotate one placement, ignoring its current cells during overlap checks. * @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement */ export declare function moveShaped(grid: ShapedGrid, id: string, origin: Cell, rotation?: Rotation): ShapedResult; /** @internal */ export declare function cellOccupant(grid: ShapedGrid, cell: Cell): string | null; export interface GridAdjacencyQuery { cellsOf(id: string): readonly Cell[]; neighborsOf(id: string): readonly string[]; touching(idA: string, idB: string): boolean; adjacentCells(cells: readonly Cell[]): readonly Cell[]; } /** @internal */ export declare function gridAdjacencyQuery(grid: ShapedGrid, options?: { diagonal?: boolean; }): GridAdjacencyQuery; /** @internal */ export declare function cellFromPoint(point: { x: number; y: number; }, cellSize: number, gridOrigin?: { x: number; y: number; }): Cell;