/** * Voxel character controller (pure model). * * The base first-person controller in `movementModel.ts` assumes flat ground at * y=0 — it integrates gravity into a `jumpOffset` and never asks the world how * tall the ground is. That is fine for a plaza demo but wrong for any game where * the player walks on placed blocks and digs down into the world: they float on * an invisible floor, clip through walls, and can never descend. * * This module resolves the player as an axis-aligned box against a set of solid * unit cells (a `SolidQuery`), so the same held-keys → intent pipeline drives a * body that stands on blocks, falls into holes, is stopped by walls, and steps * up single-block ledges. Keeping it renderer-free means the shell can drive it * from live scene objects while the descent/landing/step-up behaviour stays unit * testable without three.js. * * Cell convention matches the shell's object rendering and the voxel raycast: * cell (x,y,z) is the solid box X∈[x-0.5,x+0.5], Y∈[y,y+1], Z∈[z-0.5,z+0.5]. A * block at cell y therefore has its top face at world y+1, so a player standing * on the surface layer (cell y=-1) rests with their feet at y=0. */ import { type MovementIntent, type MovementTuningOverrides, type MotionFrameOptions } from "./movementModel.js"; export type SolidQuery = (x: number, y: number, z: number) => boolean; /** Footprint + height of the player box, in world units. */ export interface VoxelPlayerDims { /** Half the box width on both horizontal axes. */ halfWidth: number; /** Box height measured up from the feet. */ height: number; /** Tallest ledge the player walks up without jumping. */ stepHeight: number; } export declare const DEFAULT_VOXEL_DIMS: VoxelPlayerDims; /** Mutable kinematic state carried between frames by the voxel controller. */ export interface VoxelPlayerBody { x: number; y: number; z: number; velocityX: number; velocityY: number; velocityZ: number; grounded: boolean; jumpHeld: boolean; } export declare function createVoxelPlayerBody(x: number, y: number, z: number): VoxelPlayerBody; /** Samples the terrain floor height at a point; unset means flat ground plays no part in support. */ export type GroundHeightQuery = (x: number, z: number) => number; /** * Advance one frame of voxel-collided player kinematics. Mutates `body` * (position, velocity, grounded) in place. * * `forwardX`/`forwardZ` are the camera heading projected onto the ground plane * (need not be normalized). Pulling this out of the render loop is what lets us * assert "dig the block underfoot and the player drops a level" in a unit test. * * `groundHeight`, when supplied, is an additional solid floor sampled at the * body's (x, z): the player never sinks below it and standing on it counts as * supported (resets jump state like landing on a solid cell). Where terrain and * a solid cell's top disagree, the higher of the two wins. */ export declare function advanceVoxelPlayer(body: VoxelPlayerBody, intent: MovementIntent, forwardX: number, forwardZ: number, baseSpeed: number, rawDeltaSeconds: number, isSolid: SolidQuery, dims?: VoxelPlayerDims, tuning?: MovementTuningOverrides, groundHeight?: GroundHeightQuery, options?: Pick): void;