Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions PROJECT_DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ npm run build
| Procedural textures & terrain | `perlin`, `perlin3D`, `simplex2D`, `simplex3D`, `worley`, `worleySample`, `waveFunctionCollapse`, `cellularAutomataCave`, `poissonDiskSampling`, `computeVoronoiDiagram`, `diamondSquare`, `generateLSystem`, `generateBspDungeon`, `generateRecursiveMaze`, `generatePrimMaze`, `generateKruskalMaze`, `generateWilsonMaze`, `generateAldousBroderMaze`, `generateRecursiveDivisionMaze` | `procedural/*.ts` | `examples/simplex.ts`, `examples/worley.ts`, `examples/waveFunctionCollapse.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts`, `examples/mazeDivision.ts` |
| Spatial queries & collision | `Quadtree`, `aabbCollision`, `aabbIntersection`, `satCollision`, `circleRayIntersection`, `sweptAABB` | `spatial/*.ts` | `examples/sat.ts` |
| Web performance & UI throttling | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange`, `createWeightedAliasSampler`, `createObjectPool`, `fisherYatesShuffle` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts`, `examples/weightedAlias.ts`, `examples/objectPool.ts`, `examples/fisherYates.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts` | `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController`, `createTopDownController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts` | `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts`, `examples/topDownMovement.ts` |
| Text & search | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance` | `search/*.ts` | `examples/search.ts` |
| Data transforms & diffing | `diff`, `deepClone`, `groupBy`, `diffJson`, `applyJsonDiff` | `data/*.ts` | `examples/jsonDiff.ts` |
| Graph traversal | `graphBFS`, `graphDFS`, `topologicalSort` | `graph/traversal.ts` | `examples/graph.ts` |
Expand Down Expand Up @@ -94,7 +94,7 @@ Consistency between runtime code, documentation, and TypeScript declarations kee
- **Procedural:** 2D/3D Perlin, Worley noise, Wave Function Collapse tile synthesis.
- **Spatial:** Quadtree, AABB helpers, SAT convex polygon collision.
- **Performance utilities:** Debounce, throttle, LRU cache, memoize, request deduplication, virtual scrolling, weighted alias sampling, object pooling, Fisher–Yates shuffle.
- **Gameplay systems:** Delta-time manager, fixed timestep loop, 2D camera with smoothing and shake, particle system with configurable emitters, sprite animation controller with frame events, tween system with easing and repeats, platformer physics helper with coyote time and jump buffering.
- **Gameplay systems:** Delta-time manager, fixed timestep loop, 2D camera with smoothing and shake, particle system with configurable emitters, sprite animation controller with frame events, tween system with easing and repeats, platformer physics helper with coyote time and jump buffering, top-down movement controller with acceleration and drag.
- **Search:** Fuzzy search + scoring, Trie-based autocomplete, binary search, Levenshtein distance.
- **Data tools:** Diff operations (LCS), deep clone, groupBy, JSON diff/patch helpers.
- **Graph:** BFS distance map, DFS traversal, topological sort.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ CDN usage:
| Spatial queries & collision | `Quadtree`, `aabbCollision`, `aabbIntersection`, `satCollision`, `circleRayIntersection`, `sweptAABB` | `spatial/*.ts` | `examples/sat.ts` |
| AI behaviours & crowds | `seek`, `flee`, `arrive`, `pursue`, `wander`, `updateBoids`, `BehaviorTree`, `rvoStep` | `ai/steering.ts`, `ai/boids.ts`, `ai/behaviorTree.ts`, `ai/rvo.ts` | `examples/steering.ts`, `examples/boids.ts`, `examples/rvo.ts` |
| Web performance & UI | `debounce`, `throttle`, `LRUCache`, `memoize`, `deduplicateRequest`, `clearRequestDedup`, `calculateVirtualRange`, `createWeightedAliasSampler`, `createObjectPool`, `fisherYatesShuffle` | `util/*.ts` | `examples/requestDedup.ts`, `examples/virtualScroll.ts`, `examples/weightedAlias.ts`, `examples/objectPool.ts`, `examples/fisherYates.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts` | `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController`, `createTopDownController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts` | `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts`, `examples/topDownMovement.ts` |
| Search & text | `fuzzySearch`, `fuzzyScore`, `Trie`, `binarySearch`, `levenshteinDistance` | `search/*.ts` | `examples/search.ts` |
| Data & diff pipelines | `diff`, `deepClone`, `groupBy`, `diffJson`, `applyJsonDiff` | `data/*.ts` | `examples/jsonDiff.ts` |
| Graph algorithms | `graphBFS`, `graphDFS`, `topologicalSort` | `graph/traversal.ts` | `examples/graph.ts` |
Expand All @@ -53,7 +53,7 @@ npm run size # Enforce bundle size budget
- Milestone 0.2 next targets crowd-flow integrations (RVO + flow fields) and behaviour-tree decorators for richer AI control.
- Milestone 0.4 plans a procedural + gameplay systems toolkit (Wave Function Collapse, dungeon suite, L-systems, game loop, camera, particles, inventory, combat, save/load, and more).

Examples live under `examples/` and can be executed with `tsx`/`ts-node` or compiled for the browser. See `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts`, `examples/mazeDivision.ts`, `examples/steering.ts`, `examples/boids.ts`, `examples/requestDedup.ts`, `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts`, `examples/search.ts`, `examples/graph.ts`, `examples/geometry.ts`, `examples/bresenham.ts`, `examples/visual.ts`, `examples/sat.ts`, `examples/simplex.ts`, and `examples/worley.ts` for quick starts. The `examples` registry exported from `src/index.ts` provides a typed index you can traverse programmatically.
Examples live under `examples/` and can be executed with `tsx`/`ts-node` or compiled for the browser. See `examples/astar.ts`, `examples/flowField.ts`, `examples/navMesh.ts`, `examples/cellularAutomata.ts`, `examples/poissonDisk.ts`, `examples/voronoi.ts`, `examples/diamondSquare.ts`, `examples/lSystem.ts`, `examples/dungeonBsp.ts`, `examples/mazeRecursive.ts`, `examples/mazePrim.ts`, `examples/mazeKruskal.ts`, `examples/mazeWilson.ts`, `examples/mazeAldous.ts`, `examples/mazeDivision.ts`, `examples/steering.ts`, `examples/boids.ts`, `examples/requestDedup.ts`, `examples/deltaTime.ts`, `examples/fixedTimestep.ts`, `examples/camera2D.ts`, `examples/particleSystem.ts`, `examples/spriteAnimation.ts`, `examples/tween.ts`, `examples/platformerPhysics.ts`, `examples/topDownMovement.ts`, `examples/search.ts`, `examples/graph.ts`, `examples/geometry.ts`, `examples/bresenham.ts`, `examples/visual.ts`, `examples/sat.ts`, `examples/simplex.ts`, and `examples/worley.ts` for quick starts. The `examples` registry exported from `src/index.ts` provides a typed index you can traverse programmatically.

## Contributing
1. Fork the repository.
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
- [x] Sprite animation controller (frame timing, events)
- [x] Tween/lerp utility for smooth interpolation
- [x] Platformer physics helper (gravity, coyote time, jump buffering)
- [ ] Top-down movement helper (8-direction)
- [x] Top-down movement helper (8-direction)
- [ ] Tile map renderer helpers (chunking, layering, collision tags)
- [ ] Shadowcasting field-of-view utilities and minimap helpers
- **Systems for gameplay loops**
Expand Down
67 changes: 67 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1296,6 +1296,73 @@ export function createPlatformerController(
initialState?: PlatformerCharacterState
): PlatformerController;

/**
* Top-down movement options.
* Use for: configuring acceleration, deceleration, and max speed for 2D characters.
* Import: gameplay/topDownMovement.ts
*/
export interface TopDownMovementOptions {
acceleration: number;
deceleration: number;
maxSpeed: number;
drag?: number;
normalizeDiagonal?: boolean;
}

/**
* Top-down movement state snapshot.
* Use for: rendering and collision updates.
* Import: gameplay/topDownMovement.ts
*/
export interface TopDownState {
position: Vector2D;
velocity: Vector2D;
facing: Vector2D;
}

/**
* Top-down movement input axes.
* Use for: feeding directional input (-1..1).
* Import: gameplay/topDownMovement.ts
*/
export interface TopDownInput {
x: number;
y: number;
}

/**
* Top-down movement update payload.
* Use for: advancing the controller with delta time and current input.
* Import: gameplay/topDownMovement.ts
*/
export interface TopDownUpdateOptions {
delta: number;
input: TopDownInput;
}

/**
* Top-down movement controller API.
* Use for: updating state, resetting, and retuning movement parameters.
* Import: gameplay/topDownMovement.ts
*/
export interface TopDownController {
update(options: TopDownUpdateOptions): TopDownState;
getState(): TopDownState;
reset(state?: Partial<TopDownState>): void;
setOptions(options: Partial<TopDownMovementOptions>): void;
}

/**
* Creates a top-down movement controller with acceleration and damping.
* Use for: eight-direction movement in action or RPG games.
* Performance: O(1) per update.
* Import: gameplay/topDownMovement.ts
*/
export function createTopDownController(
options: TopDownMovementOptions,
initialState?: TopDownState
): TopDownController;

/**
* Least recently used cache.
* Use for: memoizing responses, data loaders, pagination caches.
Expand Down
17 changes: 17 additions & 0 deletions examples/topDownMovement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createTopDownController } from '../src/index.js';

const controller = createTopDownController({
acceleration: 20,
deceleration: 18,
maxSpeed: 6,
drag: 0.1,
});

for (let frame = 0; frame < 10; frame += 1) {
const angle = (frame / 10) * Math.PI * 2;
const input = { x: Math.cos(angle), y: Math.sin(angle) };
const state = controller.update({ delta: 1 / 30, input });
console.log(
`frame ${frame}: position=(${state.position.x.toFixed(2)}, ${state.position.y.toFixed(2)}) velocity=(${state.velocity.x.toFixed(2)}, ${state.velocity.y.toFixed(2)})`
);
}
217 changes: 217 additions & 0 deletions src/gameplay/topDownMovement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import type { Vector2D } from '../types.js';

export interface TopDownState {
position: Vector2D;
velocity: Vector2D;
facing: Vector2D;
}

export interface TopDownInput {
x: number;
y: number;
}

export interface TopDownUpdateOptions {
delta: number;
input: TopDownInput;
}

export interface TopDownMovementOptions {
acceleration: number;
deceleration: number;
maxSpeed: number;
drag?: number;
normalizeDiagonal?: boolean;
}

export interface TopDownController {
update(options: TopDownUpdateOptions): TopDownState;
getState(): TopDownState;
reset(state?: Partial<TopDownState>): void;
setOptions(options: Partial<TopDownMovementOptions>): void;
}

interface InternalOptions {
acceleration: number;
deceleration: number;
maxSpeed: number;
drag: number;
normalizeDiagonal: boolean;
}

function assertFinite(value: number, label: string): void {
if (typeof value !== 'number' || Number.isNaN(value) || !Number.isFinite(value)) {
throw new Error(`${label} must be a finite number.`);
}
}

function normalizeOptions(options: TopDownMovementOptions): InternalOptions {
assertFinite(options.acceleration, 'acceleration');
assertFinite(options.deceleration, 'deceleration');
assertFinite(options.maxSpeed, 'maxSpeed');
if (options.acceleration <= 0 || options.deceleration <= 0) {
throw new Error('acceleration and deceleration must be greater than 0.');
}
if (options.maxSpeed <= 0) {
throw new Error('maxSpeed must be greater than 0.');
}
const drag = options.drag ?? 0;
assertFinite(drag, 'drag');
if (drag < 0) {
throw new Error('drag must be >= 0.');
}
const normalizeDiagonal = options.normalizeDiagonal ?? true;

return {
acceleration: options.acceleration,
deceleration: options.deceleration,
maxSpeed: options.maxSpeed,
drag,
normalizeDiagonal,
};
}

function cloneState(state: TopDownState): TopDownState {
return {
position: { x: state.position.x, y: state.position.y },
velocity: { x: state.velocity.x, y: state.velocity.y },
facing: { x: state.facing.x, y: state.facing.y },
};
}

function magnitude(x: number, y: number): number {
return Math.hypot(x, y);
}

function normalize(x: number, y: number): Vector2D {
const length = magnitude(x, y);
if (length === 0) {
return { x: 0, y: 0 };
}
return { x: x / length, y: y / length };
}

/**
* Creates a top-down movement controller with acceleration and directional damping.
* Useful for: twin-stick or tile-based characters needing eight-direction movement.
*/
export function createTopDownController(
options: TopDownMovementOptions,
initialState: TopDownState = {
position: { x: 0, y: 0 },
velocity: { x: 0, y: 0 },
facing: { x: 1, y: 0 },
}
): TopDownController {
let config = normalizeOptions(options);
const baseline = cloneState(initialState);
const state: TopDownState = cloneState(initialState);

function setOptions(partial: Partial<TopDownMovementOptions>): void {
config = normalizeOptions({
acceleration: partial.acceleration ?? config.acceleration,
deceleration: partial.deceleration ?? config.deceleration,
maxSpeed: partial.maxSpeed ?? config.maxSpeed,
drag: partial.drag ?? config.drag,
normalizeDiagonal: partial.normalizeDiagonal ?? config.normalizeDiagonal,
});
}

function update({ delta, input }: TopDownUpdateOptions): TopDownState {
assertFinite(delta, 'delta');
if (delta < 0) {
throw new Error('delta must be >= 0.');
}
if (!input) {
throw new Error('input is required.');
}

let moveX = input.x ?? 0;
let moveY = input.y ?? 0;
if (!Number.isFinite(moveX) || !Number.isFinite(moveY)) {
throw new Error('input values must be finite numbers.');
}

if (config.normalizeDiagonal) {
const magnitudeInput = magnitude(moveX, moveY);
if (magnitudeInput > 1) {
moveX /= magnitudeInput;
moveY /= magnitudeInput;
}
}

const hasInput = Math.abs(moveX) > 1e-3 || Math.abs(moveY) > 1e-3;

if (hasInput) {
const direction = normalize(moveX, moveY);
state.velocity.x += direction.x * config.acceleration * delta;
state.velocity.y += direction.y * config.acceleration * delta;

const speed = magnitude(state.velocity.x, state.velocity.y);
if (speed > config.maxSpeed) {
const normalized = normalize(state.velocity.x, state.velocity.y);
state.velocity.x = normalized.x * config.maxSpeed;
state.velocity.y = normalized.y * config.maxSpeed;
}

state.facing.x = direction.x;
state.facing.y = direction.y;
} else {
const speed = magnitude(state.velocity.x, state.velocity.y);
if (speed > 0) {
const decelAmount = config.deceleration * delta;
if (speed <= decelAmount) {
state.velocity.x = 0;
state.velocity.y = 0;
} else {
const normalized = normalize(state.velocity.x, state.velocity.y);
const newSpeed = speed - decelAmount;
state.velocity.x = normalized.x * newSpeed;
state.velocity.y = normalized.y * newSpeed;
}
}
}

if (config.drag > 0) {
const dragFactor = Math.max(0, 1 - config.drag * delta);
state.velocity.x *= dragFactor;
state.velocity.y *= dragFactor;
}

state.position.x += state.velocity.x * delta;
state.position.y += state.velocity.y * delta;

return cloneState(state);
}

function getState(): TopDownState {
return cloneState(state);
}

function reset(partial: Partial<TopDownState> = {}): void {
const position = partial.position ?? baseline.position;
const velocity = partial.velocity ?? baseline.velocity;
const facing = partial.facing ?? baseline.facing;

state.position.x = position.x;
state.position.y = position.y;
state.velocity.x = velocity.x;
state.velocity.y = velocity.y;
state.facing.x = facing.x;
state.facing.y = facing.y;
}

return {
update,
getState,
reset,
setOptions,
};
}

/** @internal */
export const __internals = {
normalizeOptions,
normalize,
magnitude,
};
19 changes: 19 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export const examples = {
createSpriteAnimation: 'examples/spriteAnimation.ts',
createTweenSystem: 'examples/tween.ts',
createPlatformerController: 'examples/platformerPhysics.ts',
createTopDownController: 'examples/topDownMovement.ts',
},
ai: {
seek: 'examples/steering.ts',
Expand Down Expand Up @@ -570,6 +571,24 @@ export type {
PlatformerUpdateOptions,
} from './gameplay/platformerPhysics.js';

/**
* Top-down movement controller for 8-direction navigation.
*
* Example file: examples/topDownMovement.ts
*/
export { createTopDownController } from './gameplay/topDownMovement.js';

/**
* Top-down movement configuration, state, and input types.
*/
export type {
TopDownMovementOptions,
TopDownController,
TopDownState,
TopDownInput,
TopDownUpdateOptions,
} from './gameplay/topDownMovement.js';


// ============================================================================
// 🔍 SEARCH & STRING UTILITIES
Expand Down
Loading