import type { GameContext } from "../runtime/gameContext.js"; import { resolveColliders, type ResolvedCollider } from "../scene/colliders.js"; import type { EntityPosition } from "../scene/entityStore.js"; import type { WorldSolid, WorldSolids } from "../world/worldSolids.js"; import { type CollisionObstacle } from "./movementModel.js"; /** Largest blocking-collider extent in the scene, split so tower height cannot inflate the XZ query. */ export interface ObstacleReach { /** Max XZ center-to-face distance of any blocking collider (capped). */ horizontal: number; /** Max vertical center-to-face distance of any blocking collider (capped, much tighter than H). */ vertical: number; } /** * The slice of the scene's object store this query needs. Narrow on purpose: the scene context wires * its own store here while it is still being constructed, `ctx.scene.object` satisfies it as-is, and a * pure rules package with no `GameContext` to import can satisfy it from its own object list. */ export interface SolidObstacleSource { /** Optional indexed world geometry that is not represented by scene objects. */ solids?: Pick; list(): readonly { instanceId: string; position: EntityPosition; rotationY: number; }[]; inBox(min: EntityPosition, max: EntityPosition): readonly { instanceId: string; position: EntityPosition; rotationY: number; }[]; collidersOf(instanceId: string): Parameters[0] | null; } /** Mutable reach cache; one per source, invalidated by object count. */ export interface ObstacleReachCache { count: number; value: ObstacleReach; } /** A fresh {@link ObstacleReachCache} for a caller that owns its own source. */ export declare function createObstacleReachCache(): ObstacleReachCache; /** * Largest blocking reach in the scene, split into horizontal vs vertical and hard-capped. Cached per * ctx and recomputed when the object count changes — the broadphase box every consumer inflates by, so * a wall wider than the query box can never be missed. * @internal */ export declare function solidObstacleReach(ctx: GameContext): ObstacleReach; /** {@link solidObstacleReach} against a bare source and caller-owned cache. */ export declare function sourceObstacleReach(source: SolidObstacleSource, cache: ObstacleReachCache): ObstacleReach; /** One blocking collider as a {@link CollisionObstacle}: a compound `boxes` obstacle for a mesh collider * carrying a small sub-box set (openings), else a single half-extents/offset AABB. Yaw is applied by * conservatively expanding each AABB to its rotated extent (mirroring `worldOffset`), so the obstruction * never under-covers. * @internal */ export declare function obstacleFromCollider(collider: ResolvedCollider, position: EntityPosition, rotationY: number): CollisionObstacle; /** * A {@link WorldSolid} as a {@link CollisionObstacle}. Axis-aligned solids are one box; a yawed solid is * cut into strips along its long axis, each fitted with an AABB, so a turned building does not block * the street beside it the way a single enclosing box would. * @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share */ export declare function obstacleFromSolid(solid: WorldSolid): CollisionObstacle; /** * Every blocking physical obstacle overlapping the box `position` ± `reachX`/`reachZ`, read from the * scene's resolved colliders and `ctx.world.solids`. * * This is the engine's single answer to "what is solid here". The player resolver, a walking NPC, and * anything else that has to stop at a wall read it rather than deriving their own set — three * divergent notions of solid (engine colliders / game catalog flags / nothing at all) is what let a * pedestrian stroll through the building the player cannot enter. * * @capability solid-obstacles the one blocking-geometry query the player resolver and NPC movers share */ export declare function solidObstaclesNear(ctx: GameContext, position: EntityPosition, reachX: number, reachZ: number, height?: number): CollisionObstacle[]; /** {@link solidObstaclesNear} against a bare source and an already-resolved reach. */ export declare function sourceObstaclesNear(source: SolidObstacleSource, reach: ObstacleReach, position: EntityPosition, reachX: number, reachZ: number, height?: number): CollisionObstacle[]; /** * Slide a walker's horizontal step against the same solid geometry that stops the player, returning the * X/Z it may actually take this tick. A step into a wall is cut on the blocked axis and preserved on the * other, exactly as {@link stepPlayerMovement} resolves the player's. * * Bounded by the same broadphase as the player's gather: one `inBox` query sized to this step, never a * full-world scan, so it stays affordable per NPC per tick. * * The sliding is already axis-separated: X and Z are resolved independently inside, so a caller that * also splits its own movement by axis must hand this exactly one axis per call (`stepZ: 0`, then * `stepX: 0`). Running an axis-split pass over a step this function already resolved double-cuts it, * and the composition is wrong in either order. * * @capability walker-solid-step slide an NPC's step against the geometry that blocks the player */ export declare function resolveWalkerStep(ctx: GameContext, position: EntityPosition, stepX: number, stepZ: number, options?: { radius?: number; stepUpHeight?: number; }): { stepX: number; stepZ: number; }; /** * {@link resolveWalkerStep} with no `GameContext`: gather plus slide against a bare * {@link SolidObstacleSource} and a caller-owned {@link ObstacleReachCache}. * * This is the entry point for rules that live in a pure package — no React, no three.js, no engine * context to import. Hold one cache per source for the life of that source (`createObstacleReachCache()`) * rather than making a fresh one per step; it is what keeps the reach scan off the per-step path. * The same axis-separation note as {@link resolveWalkerStep} applies. * * @capability walker-solid-step-source slide a step against solid geometry from a pure rules package */ export declare function resolveSourceWalkerStep(source: SolidObstacleSource, cache: ObstacleReachCache, position: EntityPosition, stepX: number, stepZ: number, options?: { radius?: number; stepUpHeight?: number; }): { stepX: number; stepZ: number; }; /** {@link resolveWalkerStep} against an already-gathered obstacle set. */ export declare function slideStep(position: EntityPosition, stepX: number, stepZ: number, obstacles: readonly CollisionObstacle[], radius?: number, stepUpHeight?: number): { stepX: number; stepZ: number; };