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`, `createTopDownController`, `createTileMapController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts`, `gameplay/tileMap.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/tileMap.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController`, `createTopDownController`, `createTileMapController`, `computeFieldOfView` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts`, `gameplay/tileMap.ts`, `gameplay/shadowcasting.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/tileMap.ts`, `examples/shadowcasting.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, top-down movement controller with acceleration and drag, tile map renderer with chunking and collision tags.
- **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, tile map renderer with chunking and collision tags, shadowcasting FOV utility.
- **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`, `createTopDownController`, `createTileMapController` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts`, `gameplay/tileMap.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/tileMap.ts` |
| Gameplay systems | `createDeltaTimeManager`, `createFixedTimestepLoop`, `createCamera2D`, `createParticleSystem`, `createSpriteAnimation`, `createTweenSystem`, `createPlatformerController`, `createTopDownController`, `createTileMapController`, `computeFieldOfView` | `util/deltaTime.ts`, `util/fixedTimestep.ts`, `gameplay/camera2D.ts`, `gameplay/particleSystem.ts`, `gameplay/spriteAnimation.ts`, `gameplay/tween.ts`, `gameplay/platformerPhysics.ts`, `gameplay/topDownMovement.ts`, `gameplay/tileMap.ts`, `gameplay/shadowcasting.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/tileMap.ts`, `examples/shadowcasting.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/topDownMovement.ts`, `examples/tileMap.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/tileMap.ts`, `examples/shadowcasting.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 @@ -58,7 +58,7 @@
- [x] Platformer physics helper (gravity, coyote time, jump buffering)
- [x] Top-down movement helper (8-direction)
- [x] Tile map renderer helpers (chunking, layering, collision tags)
- [ ] Shadowcasting field-of-view utilities and minimap helpers
- [x] Shadowcasting field-of-view utilities and minimap helpers
- **Systems for gameplay loops**
- [ ] Inventory system primitives (stacking, filtering, persistence hooks)
- [ ] Combat resolution helpers (cooldowns, damage formulas, status effects)
Expand Down
62 changes: 62 additions & 0 deletions docs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,68 @@ export interface TileMapController {
*/
export function createTileMapController(options: TileMapOptions): TileMapController;

/**
* Shadowcasting options for field-of-view calculation.
* Use for: tuning radius, transparency, and callbacks during FOV computation.
* Import: gameplay/shadowcasting.ts
*/
export interface ShadowcastOptions {
radius: number;
transparent: (x: number, y: number) => boolean;
reveal?: (x: number, y: number) => void;
}

/**
* Field-of-view computation result.
* Use for: checking visibility of tiles.
* Import: gameplay/shadowcasting.ts
*/
export interface FovResult {
visible: Set<string>;
isVisible(x: number, y: number): boolean;
}

/**
* Boolean grid describing transparency.
* Use for: building transparency callbacks for shadowcasting.
* Import: gameplay/shadowcasting.ts
*/
export interface FovGrid {
width: number;
height: number;
tiles: ReadonlyArray<boolean>;
}

/**
* Computes field of view using recursive shadowcasting.
* Use for: roguelike FOV, fog-of-war, and lighting probes.
* Performance: O(radius^2) in practice.
* Import: gameplay/shadowcasting.ts
*/
export function computeFieldOfView(
originX: number,
originY: number,
options: ShadowcastOptions
): FovResult;

/**
* Creates a transparency function from a boolean grid.
* Use for: quick FOV prototypes.
* Import: gameplay/shadowcasting.ts
*/
export function transparentFromGrid(grid: FovGrid): (x: number, y: number) => boolean;

/**
* Creates a transparency function backed by a tile map controller.
* Use for: integrating tile maps with shadowcasting FOV.
* Import: gameplay/shadowcasting.ts
*/
export function transparentFromTileMap(
map: TileMapController,
layerName: string,
passable: (tileId: number) => boolean
): (x: number, y: number) => boolean;

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

const grid = {
width: 7,
height: 7,
tiles: [
true, true, true, true, true, true, true,
true, true, true, true, true, true, true,
true, true, false, false, false, true, true,
true, true, false, true, false, true, true,
true, true, false, false, false, true, true,
true, true, true, true, true, true, true,
true, true, true, true, true, true, true,
],
};

const transparent = transparentFromGrid(grid);
const result = computeFieldOfView(3, 3, { radius: 3, transparent });

console.log('visible tiles:', Array.from(result.visible));
226 changes: 226 additions & 0 deletions src/gameplay/shadowcasting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import type { TileMapController } from './tileMap.js';

export interface FovGrid {
width: number;
height: number;
tiles: ReadonlyArray<boolean>;
}

export interface FovResult {
visible: Set<string>;
isVisible(x: number, y: number): boolean;
}

export interface ShadowcastOptions {
radius: number;
transparent: (x: number, y: number) => boolean;
reveal?: (x: number, y: number) => void;
}

interface OctantTransform {
xx: number;
xy: number;
yx: number;
yy: number;
}

const OCTANTS: OctantTransform[] = [
{ xx: 1, xy: 0, yx: 0, yy: 1 },
{ xx: 0, xy: 1, yx: 1, yy: 0 },
{ xx: 0, xy: -1, yx: 1, yy: 0 },
{ xx: -1, xy: 0, yx: 0, yy: 1 },
{ xx: -1, xy: 0, yx: 0, yy: -1 },
{ xx: 0, xy: -1, yx: -1, yy: 0 },
{ xx: 0, xy: 1, yx: -1, yy: 0 },
{ xx: 1, xy: 0, yx: 0, yy: -1 },
];

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 encodeCoord(x: number, y: number): string {
return `${x},${y}`;
}

function castLight(
cx: number,
cy: number,
row: number,
start: number,
end: number,
radius: number,
octant: OctantTransform,
transparent: (x: number, y: number) => boolean,
reveal: (x: number, y: number) => void,
visible: Set<string>
): void {
if (start < end) {
return;
}

const radiusSquared = radius * radius;
let currentStart = start;

for (let distance = row; distance <= radius; distance += 1) {
let blocked = false;
let newStart = currentStart;

let deltaX = -distance - 1;
const deltaY = -distance;

while (deltaX <= 0) {
deltaX += 1;
const currentX = deltaX;
const currentY = deltaY;

const mapX = cx + currentX * octant.xx + currentY * octant.xy;
const mapY = cy + currentX * octant.yx + currentY * octant.yy;

const leftSlope = (currentX - 0.5) / (currentY + 0.5);
const rightSlope = (currentX + 0.5) / (currentY - 0.5);

if (rightSlope > currentStart) {
continue;
}
if (leftSlope < end) {
break;
}

if (currentX * currentX + currentY * currentY <= radiusSquared) {
reveal(mapX, mapY);
visible.add(encodeCoord(mapX, mapY));
}

const isOpaque = !transparent(mapX, mapY);
if (blocked) {
if (isOpaque) {
newStart = rightSlope;
} else {
blocked = false;
currentStart = newStart;
}
} else if (isOpaque && distance < radius) {
blocked = true;
castLight(
cx,
cy,
distance + 1,
newStart,
leftSlope,
radius,
octant,
transparent,
reveal,
visible
);
newStart = rightSlope;
}
}

if (blocked) {
break;
}
}
}

/**
* Computes visible tiles using recursive shadowcasting.
* Useful for: roguelike visibility, fog-of-war, and lighting probes.
*/
export function computeFieldOfView(
originX: number,
originY: number,
options: ShadowcastOptions
): FovResult {
assertFinite(originX, 'originX');
assertFinite(originY, 'originY');
assertFinite(options.radius, 'radius');
if (options.radius <= 0) {
throw new Error('radius must be greater than zero.');
}
if (typeof options.transparent !== 'function') {
throw new Error('transparent must be a function.');
}

const radius = Math.floor(options.radius);
const reveal = options.reveal ?? (() => {});
const visible = new Set<string>();

reveal(originX, originY);
visible.add(encodeCoord(originX, originY));

for (const octant of OCTANTS) {
castLight(
originX,
originY,
1,
1,
0,
radius,
octant,
options.transparent,
reveal,
visible
);
}

return {
visible,
isVisible(x: number, y: number) {
return visible.has(encodeCoord(x, y));
},
};
}

/**
* Helper to build a transparency callback from a boolean grid.
*/
export function transparentFromGrid(grid: FovGrid): (x: number, y: number) => boolean {
assertPositiveDimensions(grid.width, grid.height);
if (grid.tiles.length !== grid.width * grid.height) {
throw new Error('grid tiles length must be width * height.');
}
return (x: number, y: number) => {
if (x < 0 || y < 0 || x >= grid.width || y >= grid.height) {
return false;
}
return Boolean(grid.tiles[y * grid.width + x]);
};
}

function assertPositiveDimensions(width: number, height: number): void {
assertPositiveInt(width, 'width');
assertPositiveInt(height, 'height');
}

function assertPositiveInt(value: number, label: string): void {
if (!Number.isInteger(value) || value <= 0) {
throw new Error(`${label} must be a positive integer.`);
}
}

/**
* Helper to build a transparency callback that defers to a tile map controller.
*/
export function transparentFromTileMap(
map: TileMapController,
layerName: string,
passable: (tileId: number) => boolean
): (x: number, y: number) => boolean {
if (!map) {
throw new Error('tile map controller is required.');
}
if (typeof passable !== 'function') {
throw new Error('passable must be a function.');
}
return (x: number, y: number) => {
if (x < 0 || y < 0) {
return false;
}
const tile = map.getTile(layerName, x, y);
return passable(tile);
};
}
18 changes: 18 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const examples = {
createPlatformerController: 'examples/platformerPhysics.ts',
createTopDownController: 'examples/topDownMovement.ts',
createTileMapController: 'examples/tileMap.ts',
computeFieldOfView: 'examples/shadowcasting.ts',
},
ai: {
seek: 'examples/steering.ts',
Expand Down Expand Up @@ -609,6 +610,23 @@ export type {
ChunkCoordinate,
} from './gameplay/tileMap.js';

/**
* Shadowcasting field of view helpers.
*
* Example file: examples/shadowcasting.ts
*/
export {
computeFieldOfView,
transparentFromGrid,
transparentFromTileMap,
} from './gameplay/shadowcasting.js';

export type {
ShadowcastOptions,
FovResult,
FovGrid,
} from './gameplay/shadowcasting.js';


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