import type { ModelDims } from "./assetCatalog.js"; import { type CollisionMeshData, type PreparedCollisionMesh } from "./collisionMesh.js"; import type { EntityPosition } from "./entityStore.js"; export type ColliderPurpose = "physical" | "damage"; /** * Collision geometry in entity-local space. `sphere` and `aabb` are analytic; `mesh` carries a * prepared triangle mesh so opted-in concave models raycast their real surface while bounds and * broadphase keep reading the conservative `halfExtents`. * @capability mesh-hitboxes shots pass through holes in concave models — opted-in catalog assets raycast their actual triangles instead of the fitted box. * @capability mesh-movement the walking player slides against a mesh collider's compound `boxes` — a capsule walks THROUGH an opted-in archway opening and is stopped by its pillars/lintel instead of one outer box. */ export type ColliderShape = { kind: "sphere"; radius: number; offset?: EntityPosition; } | { kind: "aabb"; halfExtents: EntityPosition; offset?: EntityPosition; } | { kind: "mesh"; /** Prepared triangle mesh in model space — engine-derived from an opted-in catalog asset, never hand-authored. */ mesh: PreparedCollisionMesh; /** Uniform model→entity-local scale (same composition as the fitted box). */ meshScale: number; /** Entity-local translation applied after `meshScale`. */ meshTranslate: EntityPosition; /** Conservative entity-local AABB of the placed mesh — bounds/broadphase read this exactly like an `aabb`. */ halfExtents: EntityPosition; /** * Optional compact entity-local solid decomposition (from the model's `collisionMesh.boxes`, scaled * and translated onto the placement exactly like `mesh`). Movement obstruction slides against these * sub-boxes so a capsule walks THROUGH a concave opening; raycasts still use `mesh`. Absent when the * model shipped no `boxes`. */ boxes?: readonly { min: EntityPosition; max: EntityPosition; }[]; offset?: EntityPosition; }; export interface ColliderDef { name: string; purpose: ColliderPurpose; shape: ColliderShape; /** Defaults true for `purpose: "damage"`, false for `purpose: "physical"`. */ damageEligible?: boolean; /** Defaults true — a blocking collider stops a ray at first impact. */ blocks?: boolean; } export interface EntityColliderSet { /** Single physical collision body (push / obstruction). */ body?: ColliderDef; /** Named damage hitboxes (zero or more). */ hitboxes?: readonly ColliderDef[]; } export interface ResolvedCollider { name: string; purpose: ColliderPurpose; shape: ColliderShape; damageEligible: boolean; blocks: boolean; } export declare const DEFAULT_ENTITY_HIT_RADIUS = 0.5; export declare const DEFAULT_OBJECT_HALF_EXTENTS: EntityPosition; /** Matches the shell's fallback actor capsule (~0.7m wide, feet at y=0 to head at ~1.8m). */ export declare const DEFAULT_ENTITY_BODY_HALF_EXTENTS: EntityPosition; /** Entity-local center of the default body hitbox (half its height above the feet). */ export declare const DEFAULT_ENTITY_BODY_OFFSET: EntityPosition; /** @internal */ export declare function defaultEntityColliders(): EntityColliderSet; /** Humanoid damage box uniformly scaled to match a visually scaled mesh, kept grounded (offset stays half its height). At scale 1 this equals `defaultEntityColliders()`. * @internal */ export declare function scaledEntityColliders(scale: number): EntityColliderSet; /** @internal */ export declare function defaultObjectColliders(halfExtents?: EntityPosition): EntityColliderSet; /** Blocking physical body derived from an object's rendered scale: a grounded box spanning the visual (base at y=0, matching the shell's fallback mesh). * @internal */ export declare function scaledObjectColliders(scale: readonly [number, number, number]): EntityColliderSet; /** * The render-config subset collider fitting reads — structurally satisfied by a resolved `ModelConfig`, * so the shell can hand its render config straight to the fitting math without a conversion step. */ export interface ModelBodySource { /** Measured model-space bounds from the asset index; fitting requires `dims` with `maxY`. */ dims?: ModelDims; /** Uniform render scale. Default `1`. */ scale?: number; /** Normalize-to-height in world units — fitting composes it exactly like the renderer (`targetHeight / measured height`, multiplied by `scale`). */ targetHeight?: number; /** Vertical render offset added to the placement Y. Default `0`. */ y?: number; /** Placement registration. `"center"` (default) centers the footprint and grounds `minY` on the placement point; `"origin"` renders at the raw model origin. */ anchor?: "center" | "origin"; /** Opt-in triangle collision mesh extracted at asset reindex; when present, fitting emits a mesh-accurate shape (rays pass through holes in concave models) instead of the fitted box. */ collisionMesh?: CollisionMeshData; } /** Damage hitbox fitted to the rendered model's measured bounds — the model-aware replacement for the * fixed humanoid default. `null` when the model is unmeasured, keeping the caller's existing fallback. * @internal */ export declare function fittedEntityColliders(model: ModelBodySource): EntityColliderSet | null; /** Blocking physical body fitted to the rendered model's measured bounds — the model-aware replacement * for the unit-cube-times-scale guess. `null` when the model is unmeasured. * @internal */ export declare function fittedObjectColliders(model: ModelBodySource): EntityColliderSet | null; /** * Entity-local AABB of what the renderer actually mounted for a kind — the runtime counterpart to * index-measured `dims`. Reported through `ctx.scene.entity.reportBounds` / * `ctx.scene.object.reportBounds` (the shell does this automatically for custom * `renderEntity`/`renderObject` content and models without index dims) so collider resolution can * wrap the real rendered shape instead of a fixed-size guess. */ export interface MeasuredBounds { min: EntityPosition; max: EntityPosition; } /** Damage hitbox exactly wrapping runtime-measured render bounds — what `reportBounds` stores for an * entity kind. `null` when the bounds are degenerate, keeping the caller's existing fallback. * @internal */ export declare function measuredEntityColliders(bounds: MeasuredBounds): EntityColliderSet | null; /** Blocking physical body exactly wrapping runtime-measured render bounds — what `reportBounds` * stores for an object catalog id. `null` when the bounds are degenerate. * @internal */ export declare function measuredObjectColliders(bounds: MeasuredBounds): EntityColliderSet | null; /** Damage hitbox raycasting the actual triangles the renderer mounted for an entity kind — what * `reportCollisionMesh` stores. Triangles are entity-local (scale/placement already applied), so the * shape composes with identity `meshScale`/`meshTranslate`; bounds/broadphase read the clamped AABB. * `null` when the mesh is degenerate, keeping the caller's existing fallback. * @capability conforming-hitboxes shots aimed between a character's feet or past its shoulder MISS — the damage hitbox is the rendered model's own triangles, not a bounding box. * @internal */ export declare function meshEntityColliders(mesh: PreparedCollisionMesh): EntityColliderSet | null; /** Blocking physical body raycasting the actual triangles the renderer mounted for an object catalog * id — the mesh-accurate counterpart to `measuredObjectColliders`. Movement obstruction stays on the * conservative AABB (no `boxes` decomposition at runtime); rays hit the real surface. `null` when * degenerate. * @internal */ export declare function meshObjectColliders(mesh: PreparedCollisionMesh): EntityColliderSet | null; /** True when any collider in the set already raycasts real triangles (index-derived mesh shapes win * over runtime-measured ones — they are identical on a headless host). * @internal */ export declare function hasMeshColliderShape(set: EntityColliderSet | null | undefined): boolean; /** @internal */ export declare function resolveColliders(set: EntityColliderSet | null | undefined): ResolvedCollider[]; /** @internal */ export declare function worldOffset(local: EntityPosition | undefined, position: EntityPosition, rotationY: number): EntityPosition; /** @internal */ export declare function colliderWorldCenter(collider: ResolvedCollider, position: EntityPosition, rotationY: number): EntityPosition; /** @internal */ export declare function colliderBounds(collider: ResolvedCollider, position: EntityPosition, rotationY: number): { min: EntityPosition; max: EntityPosition; };