@boltr/geometry 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boltr/geometry",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared geometry primitives for BOLTR tactical combat game",
5
5
  "main": "src/index.js",
6
6
  "exports": {
@@ -13,12 +13,17 @@
13
13
  "scripts": {
14
14
  "test": "node --test __tests__/*.test.js"
15
15
  },
16
- "keywords": ["boltr", "geometry", "game"],
16
+ "keywords": [
17
+ "boltr",
18
+ "geometry",
19
+ "game"
20
+ ],
17
21
  "license": "UNLICENSED",
18
22
  "engines": {
19
23
  "node": ">=18.0.0"
20
24
  },
21
25
  "publishConfig": {
22
26
  "access": "public"
23
- }
27
+ },
28
+ "types": "src/index.d.ts"
24
29
  }
package/src/index.d.ts ADDED
@@ -0,0 +1,46 @@
1
+ // Re-export types
2
+ export type { Point } from './primitives';
3
+ export type { TerrainPiece } from './terrain';
4
+ export type { LOSResult, Positioned } from './los';
5
+ export type {
6
+ GameUnit,
7
+ Operative,
8
+ PathBlockResult,
9
+ WaypointValidationResult,
10
+ WaypointPathOptions,
11
+ } from './movement';
12
+
13
+ // Primitives
14
+ export {
15
+ calculateDistance,
16
+ isPointInPolygon,
17
+ lineSegmentsIntersect,
18
+ lineIntersectsPolygon,
19
+ pointToLineSegmentDistance,
20
+ pointToPolygonDistance,
21
+ lineSegmentIntersectsCircle,
22
+ isBaseOverlappingTerrain,
23
+ } from './primitives';
24
+
25
+ // Terrain
26
+ export {
27
+ BLOCKED_TERRAIN_TYPES,
28
+ PENALTY_TERRAIN_TYPES,
29
+ checkTargetOnBlockedTerrain,
30
+ checkMovementBlocked,
31
+ countPenaltyTerrainPieces,
32
+ checkTerrainMovementPenalty,
33
+ } from './terrain';
34
+
35
+ // Line of Sight
36
+ export { checkLineOfSight } from './los';
37
+
38
+ // Movement
39
+ export {
40
+ DEFAULT_BASE_RADIUS,
41
+ isPositionOccupied,
42
+ isInFriendlyExclusionZone,
43
+ isInEnemyExclusionZone,
44
+ isPathBlockedByEnemies,
45
+ validateWaypointPath,
46
+ } from './movement';
package/src/los.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { Point } from './primitives';
2
+ import { TerrainPiece } from './terrain';
3
+
4
+ /** Line of sight result. */
5
+ export type LOSResult = 'CLEAR' | 'LIGHT_COVER' | 'HEAVY_COVER' | 'BLOCKED';
6
+
7
+ /** Entity with a position, used by checkLineOfSight. */
8
+ export interface Positioned {
9
+ position: Point;
10
+ }
11
+
12
+ /**
13
+ * Check line of sight between shooter and target through terrain.
14
+ *
15
+ * - Shooter bracing on cover (within 1"): shoots across without penalty
16
+ * - Target in cover (within 1"): gets defense bonus
17
+ * - Heavy cover blocks LOS when neither operative is within 1"
18
+ * - Light cover at distance has no effect
19
+ */
20
+ export function checkLineOfSight(
21
+ shooter: Positioned,
22
+ target: Positioned,
23
+ terrain: TerrainPiece[]
24
+ ): LOSResult;
@@ -0,0 +1,98 @@
1
+ import { Point } from './primitives';
2
+ import { TerrainPiece } from './terrain';
3
+
4
+ /** Default base radius when not specified on a unit. */
5
+ export const DEFAULT_BASE_RADIUS: number;
6
+
7
+ /** A game unit with position, player ownership, and optional base radius. */
8
+ export interface GameUnit {
9
+ unit_index: number;
10
+ position: Point;
11
+ player_id: number;
12
+ eliminated?: boolean;
13
+ base_radius?: number;
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ /** The moving operative, extends GameUnit. */
18
+ export interface Operative extends GameUnit {}
19
+
20
+ /** Result of path blocking check. */
21
+ export interface PathBlockResult {
22
+ blocked: boolean;
23
+ blocker?: GameUnit;
24
+ }
25
+
26
+ /** Result of waypoint path validation. */
27
+ export interface WaypointValidationResult {
28
+ valid: boolean;
29
+ error?: string;
30
+ totalDistance?: number;
31
+ effectiveDistance?: number;
32
+ terrainPenalty?: number;
33
+ }
34
+
35
+ /** Options for validateWaypointPath. */
36
+ export interface WaypointPathOptions {
37
+ exemptEnemyUnitIndex?: number | null;
38
+ checkEnemyBlocking?: boolean;
39
+ isWithinBounds?: (point: Point, mapData?: unknown) => boolean;
40
+ }
41
+
42
+ /**
43
+ * Check if a position's base overlaps another unit's base.
44
+ */
45
+ export function isPositionOccupied(
46
+ position: Point,
47
+ baseRadius: number,
48
+ units: GameUnit[],
49
+ excludeUnitIndex?: number | null
50
+ ): boolean;
51
+
52
+ /**
53
+ * Check if a position is within the exclusion zone of any friendly operative.
54
+ * Uses sum-of-radii: two friendly bases cannot overlap.
55
+ */
56
+ export function isInFriendlyExclusionZone(
57
+ position: Point,
58
+ operative: Operative,
59
+ units: GameUnit[]
60
+ ): boolean;
61
+
62
+ /**
63
+ * Check if position is within 1" exclusion zone of any enemy operative.
64
+ */
65
+ export function isInEnemyExclusionZone(
66
+ position: Point,
67
+ operative: Operative,
68
+ units: GameUnit[]
69
+ ): boolean;
70
+
71
+ /**
72
+ * Check if a straight-line path is blocked by any enemy operatives.
73
+ * An enemy blocks a path when the line segment comes within
74
+ * (enemy.base_radius + 1.0) inches of the enemy center.
75
+ */
76
+ export function isPathBlockedByEnemies(
77
+ startPos: Point,
78
+ endPos: Point,
79
+ operative: Operative,
80
+ units: GameUnit[],
81
+ exemptUnitIndex?: number | null
82
+ ): PathBlockResult;
83
+
84
+ /**
85
+ * Validate a waypoint path: checks each segment for terrain blocking,
86
+ * enemy path blocking, and computes total distance with per-piece penalty.
87
+ */
88
+ export function validateWaypointPath(
89
+ startPos: Point,
90
+ waypoints: Point[],
91
+ baseRadius: number,
92
+ maxDistance: number,
93
+ operative: Operative,
94
+ units: GameUnit[],
95
+ terrain: TerrainPiece[],
96
+ mapData?: unknown,
97
+ options?: WaypointPathOptions
98
+ ): WaypointValidationResult;
@@ -0,0 +1,64 @@
1
+ /** A point in 2D space. */
2
+ export interface Point {
3
+ x: number;
4
+ y: number;
5
+ }
6
+
7
+ /** Euclidean distance between two points. */
8
+ export function calculateDistance(pos1: Point, pos2: Point): number;
9
+
10
+ /** Point-in-polygon test (ray casting algorithm). */
11
+ export function isPointInPolygon(point: Point, vertices: Point[]): boolean;
12
+
13
+ /**
14
+ * Test whether two line segments intersect (exclusive of endpoints).
15
+ */
16
+ export function lineSegmentsIntersect(
17
+ x1: number,
18
+ y1: number,
19
+ x2: number,
20
+ y2: number,
21
+ x3: number,
22
+ y3: number,
23
+ x4: number,
24
+ y4: number
25
+ ): boolean;
26
+
27
+ /** Test whether a line segment intersects any edge of a polygon. */
28
+ export function lineIntersectsPolygon(
29
+ lineStart: Point,
30
+ lineEnd: Point,
31
+ vertices: Point[]
32
+ ): boolean;
33
+
34
+ /** Shortest distance from a point to a line segment. */
35
+ export function pointToLineSegmentDistance(
36
+ point: Point,
37
+ lineStart: Point,
38
+ lineEnd: Point
39
+ ): number;
40
+
41
+ /** Shortest distance from a point to the boundary of a polygon. */
42
+ export function pointToPolygonDistance(point: Point, vertices: Point[]): number;
43
+
44
+ /**
45
+ * Test whether a line segment intersects a circle.
46
+ * Used for path-vs-enemy blocking checks.
47
+ */
48
+ export function lineSegmentIntersectsCircle(
49
+ lineStart: Point,
50
+ lineEnd: Point,
51
+ center: Point,
52
+ radius: number
53
+ ): boolean;
54
+
55
+ /**
56
+ * Test whether a base circle overlaps a terrain polygon.
57
+ * The center is inside the polygon OR the center-to-boundary
58
+ * distance is less than the base radius.
59
+ */
60
+ export function isBaseOverlappingTerrain(
61
+ position: Point,
62
+ baseRadius: number,
63
+ vertices: Point[]
64
+ ): boolean;
@@ -0,0 +1,53 @@
1
+ import { Point } from './primitives';
2
+
3
+ /** Terrain piece with type and polygon vertices. */
4
+ export interface TerrainPiece {
5
+ type: string;
6
+ vertices: Point[];
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ /** Terrain types that block placement / ending movement. */
11
+ export const BLOCKED_TERRAIN_TYPES: readonly string[];
12
+
13
+ /** Terrain types that add a movement penalty when crossed. */
14
+ export const PENALTY_TERRAIN_TYPES: readonly string[];
15
+
16
+ /**
17
+ * Check whether a position's base circle sits on blocked terrain.
18
+ * Returns the terrain type string if blocked, or null.
19
+ */
20
+ export function checkTargetOnBlockedTerrain(
21
+ position: Point,
22
+ baseRadius: number,
23
+ terrain: TerrainPiece[]
24
+ ): string | null;
25
+
26
+ /**
27
+ * Check whether a straight-line movement path is blocked by impassable terrain.
28
+ * Also checks whether the destination base overlaps impassable terrain.
29
+ */
30
+ export function checkMovementBlocked(
31
+ startPos: Point,
32
+ endPos: Point,
33
+ baseRadius: number,
34
+ terrain: TerrainPiece[]
35
+ ): 'path_blocked' | 'target_blocked' | null;
36
+
37
+ /**
38
+ * Count distinct terrain pieces that impose a movement penalty along a path segment.
39
+ */
40
+ export function countPenaltyTerrainPieces(
41
+ startPos: Point,
42
+ endPos: Point,
43
+ terrain: TerrainPiece[]
44
+ ): number;
45
+
46
+ /**
47
+ * Returns true if ANY penalty terrain is crossed along the path.
48
+ */
49
+ export function checkTerrainMovementPenalty(
50
+ startPos: Point,
51
+ endPos: Point,
52
+ terrain: TerrainPiece[]
53
+ ): boolean;