import type { Footprint, Vec2 } from "./geometry.js"; import type { PlacementObstacle } from "./placement.js"; /** One integer cell address on a {@link FootprintGrid}. */ export interface GridCell { col: number; row: number; } /** Config for {@link createFootprintGrid}. */ export interface FootprintGridOptions { /** World units per cell. Default 1. */ cellSize?: number; } /** A live claim on a {@link FootprintGrid}: which cells `id` (a `kind` tag for adjacency checks) holds. */ export interface FootprintReservation { id: string; kind: string; cells: readonly GridCell[]; } /** Plain JSON state of a {@link FootprintGrid}: live reservations in claim order. */ export interface FootprintGridState { reservations: FootprintReservation[]; } /** Handle returned by {@link createFootprintGrid}. */ export interface FootprintGrid { readonly cellSize: number; /** The cells a `footprint` centered on `origin` (world units) covers, honoring `quarterTurns` — feed straight into `reserve`/`isFree`. */ cellsFor(origin: Vec2, footprint: Footprint, quarterTurns?: number): GridCell[]; isFree(cells: readonly GridCell[]): boolean; /** Claims every cell for `id`/`kind`; fails (no partial reservation) if `id` already holds a reservation or any cell is occupied. */ reserve(id: string, kind: string, cells: readonly GridCell[]): boolean; release(id: string): boolean; occupantAt(cell: GridCell): string | null; kindAt(cell: GridCell): string | null; reservationOf(id: string): FootprintReservation | null; list(): readonly FootprintReservation[]; clear(): void; snapshot(): FootprintGridState; /** Replaces every reservation with `next`'s and rebuilds cell occupancy from them. */ restore(next: FootprintGridState): void; } /** * Multi-cell footprint occupancy/reservation on a shared build grid — `world/placementController` * only owns the ghost preview; this is the persistent claim a committed placement holds so the next * hover's `isFree` check (or another player's, in a shared world) sees it. Bridge into * `world/placement`'s `PlacementRules.obstacles` with {@link footprintObstacles} instead of * hand-rolling an occupancy map per game. * * @capability footprint-grid multi-cell footprint occupancy/reservation on a shared build grid */ export declare function createFootprintGrid(options?: FootprintGridOptions): FootprintGrid; /** One occupied neighbor cell reported by {@link boundaryNeighbors}. */ export interface AdjacentCell { cell: GridCell; kind: string; } /** Every occupied cell orthogonally touching `cells` but outside them — the connective-piece neighbor set. */ export declare function boundaryNeighbors(grid: FootprintGrid, cells: readonly GridCell[]): AdjacentCell[]; /** * Connective-piece adjacency validity: every occupied neighbor of `cells` must satisfy `accepts` * (no incompatible piece touching), and when `requireConnection` is true at least one neighbor must * (a road/pipe/belt segment placed with nothing to connect to is invalid). An empty-bordered footprint * (no occupied neighbors at all) passes unless `requireConnection` demands one. */ export declare function hasValidAdjacency(grid: FootprintGrid, cells: readonly GridCell[], accepts: (neighborKind: string) => boolean, requireConnection?: boolean): boolean; /** Caller-owned logical footprint. Cells must be unique, safe integer addresses and cardinally connected. */ export interface RegionFootprint { readonly id: string; readonly kind: string; readonly tier: number; readonly cells: readonly GridCell[]; /** Nonnegative finite contribution; independent of occupied area. */ readonly capacity: number; } /** Original footprint identity and capacity retained when regions pool. */ export interface RegionContribution { readonly id: string; readonly cells: readonly GridCell[]; readonly capacity: number; } /** Plain JSON pooled region. Contributions partition its cells and sum to its capacity. */ export interface MergedRegion extends RegionFootprint { readonly contributions: readonly RegionContribution[]; } /** Optional caller work limits, checked before cell traversal. Omitted limits follow input size. */ export interface RegionWorkBudget { readonly maxCells?: number; readonly maxContributions?: number; } /** Identities for split sides containing multiple contributions; singletons retain their original id. */ export interface RegionSplitIds { readonly left?: string; readonly right?: string; } /** A standalone footprint or a pooled region carrying its original contributions. */ export type FootprintRegion = RegionFootprint | MergedRegion; /** Matching kind/tier, disjoint valid source identities/cells, and cardinal contact. Linear in input size. * @capability mergeable-footprint-regions check compatible adjacent regions within caller work budgets */ export declare function canMerge(a: FootprintRegion, b: FootprintRegion, budget?: RegionWorkBudget): boolean; /** * Pool compatible footprints under caller-owned `id`, retaining original capacities and identities. * Returns null for invalid data, kind/tier mismatch, overlap, no cardinal contact or exhausted budget. * Does not mutate inputs or grid reservations; costs, merge width and upgrade policy remain caller-owned. * Capacity is the ordered JavaScript-number sum of original contributions, independent of cell area. * * @capability merge-footprint-regions pool adjacent compatible footprints with serializable capacity provenance */ export declare function mergeFootprints(a: FootprintRegion, b: FootprintRegion, id: string, budget?: RegionWorkBudget): MergedRegion | null; /** * Partition whole original contributions by `leftIds`; both sides must remain cardinally connected. * Singletons restore their original id and capacity; larger sides require explicit output identities. * Returns null for invalid provenance/budget, unknown or duplicate ids, empty/disconnected sides, or * conflicting output identities (including identities from the opposite side's contributions). * An output may reuse its own side's source identity. Cell cuts/capacity repricing require caller policy. * Both sides inherit the current region kind/tier. Inputs and grid reservations remain unchanged. * * @capability split-footprint-regions split pooled regions along retained contributions without area-based capacity loss */ export declare function splitRegion(region: MergedRegion, leftIds: readonly string[], ids?: RegionSplitIds, budget?: RegionWorkBudget): [FootprintRegion, FootprintRegion] | null; /** Bridges live reservations into `world/placement`'s `PlacementRules.obstacles` so `validatePlacement`/`createPlacementController` see the grid's committed footprints unchanged. */ export declare function footprintObstacles(grid: FootprintGrid): PlacementObstacle[];