@zylem/game-lib 0.5.1 → 0.6.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/actions/behaviors/boundaries/boundary.ts","../src/lib/collision/collision-builder.ts","../src/lib/collision/utils.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-collision.ts","../src/lib/actions/behaviors/ricochet/ricochet.ts","../src/lib/actions/behaviors/ricochet/ricochet-2d-in-bounds.ts"],"sourcesContent":["import { UpdateContext } from \"../../../core/base-node-life-cycle\";\nimport { MoveableEntity } from \"../../capabilities/moveable\";\nimport { Vector } from \"@dimforge/rapier3d-compat\";\nimport { BehaviorCallbackType } from \"../../../entities/entity\";\n\nexport interface BoundaryEvent {\n\tme: MoveableEntity;\n\tboundary: BoundaryHits;\n\tposition: Vector;\n\tupdateContext: UpdateContext<MoveableEntity>;\n}\n\nexport interface BoundaryOptions {\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tonBoundary?: (event: BoundaryEvent) => void;\n\tstopMovement?: boolean;\n}\n\nconst defaultBoundaryOptions: BoundaryOptions = {\n\tboundaries: {\n\t\ttop: 0,\n\t\tbottom: 0,\n\t\tleft: 0,\n\t\tright: 0\n\t},\n\tstopMovement: true\n};\n\n/**\n * Checks if the entity has hit a boundary and stops its movement if it has\n * \n * @param options Configuration options for the boundary behavior\n * @param options.boundaries The boundaries of the stage\n * @param options.onBoundary A callback function that is called when the entity hits a boundary\n * @param options.stopMovement Whether to stop the entity's movement when it hits a boundary\n * @returns A behavior callback with type 'update' and a handler function\n */\nexport function boundary2d(\n\toptions: Partial<BoundaryOptions> = {}\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_boundary2d(updateContext, options);\n\t\t}\n\t};\n}\n\ntype BoundaryHit = 'top' | 'bottom' | 'left' | 'right';\n\ntype BoundaryHits = Record<BoundaryHit, boolean>;\n\nfunction _boundary2d(updateContext: UpdateContext<MoveableEntity>, options: Partial<BoundaryOptions>) {\n\tconst { me: entity } = updateContext;\n\tconst { boundaries, onBoundary } = {\n\t\t...defaultBoundaryOptions,\n\t\t...options\n\t};\n\n\tconst position = entity.getPosition();\n\tif (!position) return;\n\n\tlet boundariesHit: BoundaryHits = { top: false, bottom: false, left: false, right: false };\n\n\tif (position.x <= boundaries.left) {\n\t\tboundariesHit.left = true;\n\t} else if (position.x >= boundaries.right) {\n\t\tboundariesHit.right = true;\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tboundariesHit.bottom = true;\n\t} else if (position.y >= boundaries.top) {\n\t\tboundariesHit.top = true;\n\t}\n\n\tconst stopMovement = options.stopMovement ?? true;\n\tif (stopMovement && boundariesHit) {\n\t\tconst velocity = entity.getVelocity() ?? { x: 0, y: 0, z: 0 };\n\t\tlet { x: newX, y: newY } = velocity;\n\t\tif (velocity?.y < 0 && boundariesHit.bottom) {\n\t\t\tnewY = 0;\n\t\t} else if (velocity?.y > 0 && boundariesHit.top) {\n\t\t\tnewY = 0;\n\t\t}\n\t\tif (velocity?.x < 0 && boundariesHit.left) {\n\t\t\tnewX = 0;\n\t\t} else if (velocity?.x > 0 && boundariesHit.right) {\n\t\t\tnewX = 0;\n\t\t}\n\t\tentity.moveXY(newX, newY);\n\t}\n\tif (onBoundary && boundariesHit) {\n\t\tonBoundary({\n\t\t\tme: entity,\n\t\t\tboundary: boundariesHit,\n\t\t\tposition: { x: position.x, y: position.y, z: position.z },\n\t\t\tupdateContext\n\t\t});\n\t}\n}","import { ActiveCollisionTypes, ColliderDesc, RigidBodyDesc, RigidBodyType, Vector3 } from \"@dimforge/rapier3d-compat\";\nimport { Vec3 } from \"../core/vector\";\nimport { CollisionOptions } from \"./collision\";\n\nconst typeToGroup = new Map<string, number>();\nlet nextGroupId = 0;\n\nexport function getOrCreateCollisionGroupId(type: string): number {\n\tlet groupId = typeToGroup.get(type);\n\tif (groupId === undefined) {\n\t\tgroupId = nextGroupId++ % 16;\n\t\ttypeToGroup.set(type, groupId);\n\t}\n\treturn groupId;\n}\n\nexport function createCollisionFilter(allowedTypes: string[]): number {\n\tlet filter = 0;\n\tallowedTypes.forEach(type => {\n\t\tconst groupId = getOrCreateCollisionGroupId(type);\n\t\tfilter |= (1 << groupId);\n\t});\n\treturn filter;\n}\n\nexport class CollisionBuilder {\n\tstatic: boolean = false;\n\tsensor: boolean = false;\n\tgravity: Vec3 = new Vector3(0, 0, 0);\n\n\tbuild(options: Partial<CollisionOptions>): [RigidBodyDesc, ColliderDesc] {\n\t\tconst bodyDesc = this.bodyDesc({\n\t\t\tisDynamicBody: !this.static\n\t\t});\n\t\tconst collider = this.collider(options);\n\t\tconst type = options.collisionType;\n\t\tif (type) {\n\t\t\tlet groupId = getOrCreateCollisionGroupId(type);\n\t\t\tlet filter = 0b1111111111111111;\n\t\t\tif (options.collisionFilter) {\n\t\t\t\tfilter = createCollisionFilter(options.collisionFilter);\n\t\t\t}\n\t\t\tcollider.setCollisionGroups((groupId << 16) | filter);\n\t\t}\n\t\tconst { KINEMATIC_FIXED, DEFAULT } = ActiveCollisionTypes;\n\t\tcollider.activeCollisionTypes = (this.sensor) ? KINEMATIC_FIXED : DEFAULT;\n\t\treturn [bodyDesc, collider];\n\t}\n\n\twithCollision(collisionOptions: Partial<CollisionOptions>): this {\n\t\tthis.sensor = collisionOptions?.sensor ?? this.sensor;\n\t\tthis.static = collisionOptions?.static ?? this.static;\n\t\treturn this;\n\t}\n\n\tcollider(options: CollisionOptions): ColliderDesc {\n\t\tconst size = options.size ?? new Vector3(1, 1, 1);\n\t\tconst half = { x: size.x / 2, y: size.y / 2, z: size.z / 2 };\n\t\tlet colliderDesc = ColliderDesc.cuboid(half.x, half.y, half.z);\n\t\treturn colliderDesc;\n\t}\n\n\tbodyDesc({ isDynamicBody = true }): RigidBodyDesc {\n\t\tconst type = isDynamicBody ? RigidBodyType.Dynamic : RigidBodyType.Fixed;\n\t\tconst bodyDesc = new RigidBodyDesc(type)\n\t\t\t.setTranslation(0, 0, 0)\n\t\t\t.setGravityScale(1.0)\n\t\t\t.setCanSleep(false)\n\t\t\t.setCcdEnabled(true);\n\t\treturn bodyDesc;\n\t}\n}","import type { GameEntity } from \"../entities/entity\";\nimport type { CollisionMask } from \"./collision-mask\";\nimport { getOrCreateCollisionGroupId } from \"./collision-builder\";\n\nexport type NameSelector = string | string[] | RegExp;\n\nexport type CollisionSelector =\n\t| { name: NameSelector }\n\t| { mask: CollisionMask | RegExp }\n\t| { test: (other: GameEntity<any>) => boolean };\n\n/**\n * Returns true if the `other` entity matches the provided selector.\n */\nexport function matchesCollisionSelector(other: GameEntity<any>, selector?: CollisionSelector): boolean {\n\tif (!selector) return true;\n\tconst otherName = other.name ?? '';\n\tif ('name' in selector) {\n\t\tconst sel = selector.name as NameSelector;\n\t\tif (sel instanceof RegExp) {\n\t\t\treturn sel.test(otherName);\n\t\t} else if (Array.isArray(sel)) {\n\t\t\treturn sel.some(s => s === otherName);\n\t\t} else {\n\t\t\treturn otherName === sel;\n\t\t}\n\t} else if ('mask' in selector) {\n\t\tconst m = selector.mask as CollisionMask | RegExp;\n\t\tif (m instanceof RegExp) {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\treturn m.test(type);\n\t\t} else {\n\t\t\tconst type = other.collisionType ?? '';\n\t\t\tconst gid = getOrCreateCollisionGroupId(type);\n\t\t\treturn ((m as number) & (1 << gid)) !== 0;\n\t\t}\n\t} else if ('test' in selector) {\n\t\treturn !!selector.test(other);\n\t}\n\treturn true;\n}\n\n\n","import { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { matchesCollisionSelector } from '../../../collision/utils';\nimport { Ricochet2DCollisionOptions, clamp, Ricochet2DCollisionCallback } from './ricochet';\n\n/**\n * Behavior for ricocheting an entity off other objects in 2D\n */\nexport function ricochet2DCollision(\n\toptions: Partial<Ricochet2DCollisionOptions> = {},\n\tcallback?: Ricochet2DCollisionCallback\n): { type: 'collision'; handler: (ctx: CollisionContext<MoveableEntity, GameEntity<any>>) => void } {\n\treturn {\n\t\ttype: 'collision',\n\t\thandler: (collisionContext: CollisionContext<MoveableEntity, GameEntity<any>>) => {\n\t\t\t_handleRicochet2DCollision(collisionContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DCollision(\n\tcollisionContext: CollisionContext<MoveableEntity, GameEntity<any>>,\n\toptions: Partial<Ricochet2DCollisionOptions>,\n\tcallback?: Ricochet2DCollisionCallback\n) {\n\tconst { entity, other } = collisionContext;\n\tconst self = entity as unknown as GameEntity<any> & MoveableEntity;\n\tif (other.collider?.isSensor()) return;\n\n\tconst {\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tseparation = 0,\n\t\tcollisionWith = undefined,\n\t} = {\n\t\t...options,\n\t} as Ricochet2DCollisionOptions;\n\tconst reflectionMode: 'simple' | 'angled' = (options as any)?.reflectionMode ?? 'angled';\n\tconst maxAngleDeg = (options as any)?.maxAngleDeg ?? 60;\n\tconst speedUpFactor = (options as any)?.speedUpFactor ?? 1.05;\n\tconst minOffsetForAngle = (options as any)?.minOffsetForAngle ?? 0.15; // 0..1\n\tconst centerRetentionFactor = (options as any)?.centerRetentionFactor ?? 0.5; // keep some Y on center hits\n\n\tif (!matchesCollisionSelector(other, collisionWith)) return;\n\n\tconst selfPos = self.getPosition();\n\tconst otherPos = other.body?.translation();\n\tconst vel = self.getVelocity();\n\tif (!selfPos || !otherPos || !vel) return;\n\n\tlet newVelX = vel.x;\n\tlet newVelY = vel.y;\n\tlet newX = selfPos.x;\n\tlet newY = selfPos.y;\n\n\tconst dx = selfPos.x - otherPos.x;\n\tconst dy = selfPos.y - otherPos.y;\n\n\tlet extentX: number | null = null;\n\tlet extentY: number | null = null;\n\tconst colliderShape: any = other.collider?.shape as any;\n\tif (colliderShape) {\n\t\tif (colliderShape.halfExtents) {\n\t\t\textentX = Math.abs(colliderShape.halfExtents.x ?? colliderShape.halfExtents[0] ?? null);\n\t\t\textentY = Math.abs(colliderShape.halfExtents.y ?? colliderShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((extentX == null || extentY == null) && (typeof colliderShape.radius === 'number')) {\n\t\t\textentX = extentX ?? Math.abs(colliderShape.radius);\n\t\t\textentY = extentY ?? Math.abs(colliderShape.radius);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.halfExtents === 'function') {\n\t\tconst he = (other.collider as any).halfExtents();\n\t\tif (he) {\n\t\t\textentX = extentX ?? Math.abs(he.x);\n\t\t\textentY = extentY ?? Math.abs(he.y);\n\t\t}\n\t}\n\tif ((extentX == null || extentY == null) && typeof (other.collider as any)?.radius === 'function') {\n\t\tconst r = (other.collider as any).radius();\n\t\tif (typeof r === 'number') {\n\t\t\textentX = extentX ?? Math.abs(r);\n\t\t\textentY = extentY ?? Math.abs(r);\n\t\t}\n\t}\n\n\tlet relX = 0;\n\tlet relY = 0;\n\tif (extentX && extentY) {\n\t\trelX = clamp(dx / extentX, -1, 1);\n\t\trelY = clamp(dy / extentY, -1, 1);\n\t} else {\n\t\trelX = Math.sign(dx);\n\t\trelY = Math.sign(dy);\n\t}\n\tlet bounceVertical = Math.abs(dy) >= Math.abs(dx);\n\n\tlet selfExtentX: number | null = null;\n\tlet selfExtentY: number | null = null;\n\tconst selfShape: any = self.collider?.shape as any;\n\tif (selfShape) {\n\t\tif (selfShape.halfExtents) {\n\t\t\tselfExtentX = Math.abs(selfShape.halfExtents.x ?? selfShape.halfExtents[0] ?? null);\n\t\t\tselfExtentY = Math.abs(selfShape.halfExtents.y ?? selfShape.halfExtents[1] ?? null);\n\t\t}\n\t\tif ((selfExtentX == null || selfExtentY == null) && (typeof selfShape.radius === 'number')) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(selfShape.radius);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(selfShape.radius);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.halfExtents === 'function') {\n\t\tconst heS = (self.collider as any).halfExtents();\n\t\tif (heS) {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(heS.x);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(heS.y);\n\t\t}\n\t}\n\tif ((selfExtentX == null || selfExtentY == null) && typeof (self.collider as any)?.radius === 'function') {\n\t\tconst rS = (self.collider as any).radius();\n\t\tif (typeof rS === 'number') {\n\t\t\tselfExtentX = selfExtentX ?? Math.abs(rS);\n\t\t\tselfExtentY = selfExtentY ?? Math.abs(rS);\n\t\t}\n\t}\n\n\tif (extentX != null && extentY != null && selfExtentX != null && selfExtentY != null) {\n\t\tconst penX = (selfExtentX + extentX) - Math.abs(dx);\n\t\tconst penY = (selfExtentY + extentY) - Math.abs(dy);\n\t\tif (!Number.isNaN(penX) && !Number.isNaN(penY)) {\n\t\t\tbounceVertical = penY <= penX;\n\t\t}\n\t}\n\n\tlet usedAngleDeflection = false;\n\tif (bounceVertical) {\n\t\tconst resolvedY = (extentY ?? 0) + (selfExtentY ?? 0) + separation;\n\t\tnewY = otherPos.y + (dy > 0 ? resolvedY : -resolvedY);\n\t\tnewX = selfPos.x;\n\n\t\tconst isHorizontalPaddle = extentX != null && extentY != null && extentX > extentY;\n\t\tif (isHorizontalPaddle && reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetX = clamp(relX, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetX);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetX) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vy = Math.abs(speed * cosA);\n\t\t\t\tconst vx = speed * sinA;\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t} else {\n\t\t\t\tconst vx = vel.x * centerRetentionFactor;\n\t\t\t\tconst vyMagSquared = Math.max(0, speed * speed - vx * vx);\n\t\t\t\tconst vy = Math.sqrt(vyMagSquared);\n\t\t\t\tnewVelY = dy > 0 ? vy : -vy;\n\t\t\t\tnewVelX = vx;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\t// Simple vertical reflection (or non-paddle surface)\n\t\t\tnewVelY = dy > 0 ? Math.abs(vel.y) : -Math.abs(vel.y);\n\t\t\tif (reflectionMode === 'simple') usedAngleDeflection = true;\n\t\t}\n\t} else {\n\t\tconst resolvedX = (extentX ?? 0) + (selfExtentX ?? 0) + separation;\n\t\tnewX = otherPos.x + (dx > 0 ? resolvedX : -resolvedX);\n\t\tnewY = selfPos.y;\n\n\t\tif (reflectionMode === 'angled') {\n\t\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\t\t\tconst deadzone = Math.max(0, Math.min(1, minOffsetForAngle));\n\t\t\tconst clampedOffsetY = clamp(relY, -1, 1);\n\t\t\tconst absOff = Math.abs(clampedOffsetY);\n\t\t\tconst baseSpeed = Math.sqrt(vel.x * vel.x + vel.y * vel.y);\n\t\t\tconst speed = clamp(baseSpeed * speedUpFactor, minSpeed, maxSpeed);\n\t\t\tif (absOff > deadzone) {\n\t\t\t\tconst t = (absOff - deadzone) / (1 - deadzone);\n\t\t\t\tconst angle = Math.sign(clampedOffsetY) * (t * maxAngleRad);\n\t\t\t\tconst cosA = Math.cos(angle);\n\t\t\t\tconst sinA = Math.sin(angle);\n\t\t\t\tconst vx = Math.abs(speed * cosA);\n\t\t\t\tconst vy = speed * sinA;\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t} else {\n\t\t\t\tconst vy = vel.y * centerRetentionFactor;\n\t\t\t\tconst vxMagSquared = Math.max(0, speed * speed - vy * vy);\n\t\t\t\tconst vx = Math.sqrt(vxMagSquared);\n\t\t\t\tnewVelX = dx > 0 ? vx : -vx;\n\t\t\t\tnewVelY = vy;\n\t\t\t}\n\t\t\tusedAngleDeflection = true;\n\t\t} else {\n\t\t\tnewVelX = dx > 0 ? Math.abs(vel.x) : -Math.abs(vel.x);\n\t\t\tnewVelY = vel.y;\n\t\t\tusedAngleDeflection = true;\n\t\t}\n\t}\n\n\tif (!usedAngleDeflection) {\n\t\tconst additionBaseX = Math.abs(newVelX);\n\t\tconst additionBaseY = Math.abs(newVelY);\n\t\tconst addX = Math.sign(relX) * Math.abs(relX) * additionBaseX;\n\t\tconst addY = Math.sign(relY) * Math.abs(relY) * additionBaseY;\n\t\tnewVelX += addX;\n\t\tnewVelY += addY;\n\t}\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (newX !== selfPos.x || newY !== selfPos.y) {\n\t\tself.setPosition(newX, newY, selfPos.z);\n\t\tself.moveXY(newVelX, newVelY);\n\t\tif (callback) {\n\t\t\tconst velocityAfter = self.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tposition: { x: newX, y: newY, z: selfPos.z },\n\t\t\t\t\t...collisionContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}","import { Vector } from '@dimforge/rapier3d-compat';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { CollisionContext, GameEntity } from '../../../entities/entity';\nimport { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { CollisionSelector } from '../../../collision/utils';\n\nexport interface RicochetEvent extends Partial<UpdateContext<MoveableEntity>> {\n\tboundary?: 'top' | 'bottom' | 'left' | 'right';\n\tposition: Vector;\n\tvelocityBefore: Vector;\n\tvelocityAfter: Vector;\n}\n\nexport interface RicochetCollisionEvent extends CollisionContext<MoveableEntity, GameEntity<any>> {\n\tposition: Vector;\n}\n\nexport interface Ricochet2DInBoundsOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tboundaries: {\n\t\ttop: number;\n\t\tbottom: number;\n\t\tleft: number;\n\t\tright: number;\n\t};\n\tseparation?: number;\n}\n\nexport interface Ricochet2DCollisionOptions {\n\trestitution?: number;\n\tminSpeed?: number;\n\tmaxSpeed?: number;\n\tseparation?: number;\n\tcollisionWith?: CollisionSelector;\n\t/**\n\t * Choose between simple axis inversion or angled (paddle-style) reflection.\n\t * Defaults to 'angled'.\n\t */\n\treflectionMode?: 'simple' | 'angled';\n}\n\nexport type Ricochet2DCallback = (event: RicochetEvent) => void;\nexport type Ricochet2DCollisionCallback = (event: RicochetCollisionEvent) => void;\n\nexport function clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nexport { ricochet2DInBounds } from './ricochet-2d-in-bounds';\nexport { ricochet2DCollision } from './ricochet-2d-collision';","import { UpdateContext } from '../../../core/base-node-life-cycle';\nimport { MoveableEntity } from '../../capabilities/moveable';\nimport { Ricochet2DInBoundsOptions, Ricochet2DCallback, clamp } from './ricochet';\nimport { BehaviorCallbackType } from '../../../entities/entity';\n\n/**\n * Behavior for ricocheting an entity within fixed 2D boundaries\n */\nexport function ricochet2DInBounds(\n\toptions: Partial<Ricochet2DInBoundsOptions> = {},\n\tcallback?: Ricochet2DCallback\n): { type: BehaviorCallbackType; handler: (ctx: UpdateContext<MoveableEntity>) => void } {\n\treturn {\n\t\ttype: 'update' as BehaviorCallbackType,\n\t\thandler: (updateContext: UpdateContext<MoveableEntity>) => {\n\t\t\t_handleRicochet2DInBounds(updateContext, options, callback);\n\t\t},\n\t};\n}\n\nfunction _handleRicochet2DInBounds(\n\tupdateContext: UpdateContext<MoveableEntity>,\n\toptions: Partial<Ricochet2DInBoundsOptions>,\n\tcallback?: Ricochet2DCallback\n) {\n\tconst { me } = updateContext;\n\tconst {\n\t\trestitution = 0,\n\t\tminSpeed = 2,\n\t\tmaxSpeed = 20,\n\t\tboundaries = { top: 5, bottom: -5, left: -6.5, right: 6.5 },\n\t\tseparation = 0.0\n\t} = { ...options } as Ricochet2DInBoundsOptions;\n\n\tconst position = me.getPosition();\n\tconst velocity = me.getVelocity();\n\tif (!position || !velocity) return;\n\n\tlet newVelX = velocity.x;\n\tlet newVelY = velocity.y;\n\tlet newX = position.x;\n\tlet newY = position.y;\n\tlet ricochetBoundary: 'top' | 'bottom' | 'left' | 'right' | null = null;\n\n\tif (position.x <= boundaries.left) {\n\t\tnewVelX = Math.abs(velocity.x);\n\t\tnewX = boundaries.left + separation;\n\t\tricochetBoundary = 'left';\n\t} else if (position.x >= boundaries.right) {\n\t\tnewVelX = -Math.abs(velocity.x);\n\t\tnewX = boundaries.right - separation;\n\t\tricochetBoundary = 'right';\n\t}\n\n\tif (position.y <= boundaries.bottom) {\n\t\tnewVelY = Math.abs(velocity.y);\n\t\tnewY = boundaries.bottom + separation;\n\t\tricochetBoundary = 'bottom';\n\t} else if (position.y >= boundaries.top) {\n\t\tnewVelY = -Math.abs(velocity.y);\n\t\tnewY = boundaries.top - separation;\n\t\tricochetBoundary = 'top';\n\t}\n\n\tconst currentSpeed = Math.sqrt(newVelX * newVelX + newVelY * newVelY);\n\tif (currentSpeed > 0) {\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tif (targetSpeed !== currentSpeed) {\n\t\t\tconst scale = targetSpeed / currentSpeed;\n\t\t\tnewVelX *= scale;\n\t\t\tnewVelY *= scale;\n\t\t}\n\t}\n\n\tif (restitution) {\n\t\tnewVelX *= restitution;\n\t\tnewVelY *= restitution;\n\t}\n\n\tif (newX !== position.x || newY !== position.y) {\n\t\tme.setPosition(newX, newY, position.z);\n\t\tme.moveXY(newVelX, newVelY);\n\n\t\tif (callback && ricochetBoundary) {\n\t\t\tconst velocityAfter = me.getVelocity();\n\t\t\tif (velocityAfter) {\n\t\t\t\tcallback({\n\t\t\t\t\tboundary: ricochetBoundary,\n\t\t\t\t\tposition: { x: newX, y: newY, z: position.z },\n\t\t\t\t\tvelocityBefore: velocity,\n\t\t\t\t\tvelocityAfter,\n\t\t\t\t\t...updateContext,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/lib/behaviors/behavior-descriptor.ts","../src/lib/behaviors/use-behavior.ts","../src/lib/behaviors/components.ts","../src/lib/behaviors/physics-step.behavior.ts","../src/lib/behaviors/physics-sync.behavior.ts","../src/lib/behaviors/thruster/components.ts","../src/lib/behaviors/thruster/thruster-fsm.ts","../src/lib/behaviors/thruster/thruster-movement.behavior.ts","../src/lib/behaviors/thruster/thruster.descriptor.ts","../src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts","../src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts","../src/lib/behaviors/world-boundary-2d/world-boundary-2d-fsm.ts","../src/lib/behaviors/world-boundary-2d/world-boundary-2d.descriptor.ts","../src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts","../src/lib/behaviors/ricochet-2d/ricochet-2d.descriptor.ts","../src/lib/behaviors/movement-sequence-2d/movement-sequence-2d-fsm.ts","../src/lib/behaviors/movement-sequence-2d/movement-sequence-2d.descriptor.ts","../src/lib/coordinators/boundary-ricochet.coordinator.ts"],"sourcesContent":["/**\n * BehaviorDescriptor\n *\n * Type-safe behavior descriptors that provide options inference.\n * Used with entity.use() to declaratively attach behaviors to entities.\n *\n * Each behavior can define its own handle type via `createHandle`,\n * providing behavior-specific methods with full type safety.\n */\n\nimport type { BehaviorSystemFactory } from './behavior-system';\n\n/**\n * Base handle returned by entity.use() for lazy access to behavior runtime.\n * FSM is null until entity is spawned and components are initialized.\n */\nexport interface BaseBehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** Get the FSM instance (null until entity is spawned) */\n getFSM(): any | null;\n /** Get the current options */\n getOptions(): O;\n /** Access the underlying behavior ref */\n readonly ref: BehaviorRef<O>;\n}\n\n/**\n * Reference to a behavior stored on an entity\n */\nexport interface BehaviorRef<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** The behavior descriptor */\n descriptor: BehaviorDescriptor<O, any>;\n /** Merged options (defaults + overrides) */\n options: O;\n /** Optional FSM instance - set lazily when entity is spawned */\n fsm?: any;\n}\n\n/**\n * A typed behavior descriptor that associates a symbol key with:\n * - Default options (providing type inference)\n * - A system factory to create the behavior system\n * - An optional handle factory for behavior-specific methods\n */\nexport interface BehaviorDescriptor<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Unique symbol identifying this behavior */\n readonly key: symbol;\n /** Default options (used for type inference) */\n readonly defaultOptions: O;\n /** Factory to create the behavior system */\n readonly systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * These methods are merged into the handle returned by entity.use().\n */\n readonly createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * The full handle type returned by entity.use().\n * Combines base handle with behavior-specific methods.\n */\nexport type BehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n> = BaseBehaviorHandle<O> & H;\n\n/**\n * Configuration for defining a new behavior\n */\nexport interface DefineBehaviorConfig<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Human-readable name for debugging */\n name: string;\n /** Default options - these define the type */\n defaultOptions: O;\n /** Factory function to create the system */\n systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * The returned object is merged into the handle returned by entity.use().\n *\n * @example\n * ```typescript\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX, moveY) => ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * ```\n */\n createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * Define a typed behavior descriptor.\n *\n * @example\n * ```typescript\n * export const WorldBoundary2DBehavior = defineBehavior({\n * name: 'world-boundary-2d',\n * defaultOptions: { boundaries: { top: 0, bottom: 0, left: 0, right: 0 } },\n * systemFactory: (ctx) => new WorldBoundary2DSystem(ctx.world),\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX: number, moveY: number) =>\n * ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * });\n *\n * // Usage - handle has getLastHits and getMovement with full types\n * const boundary = ship.use(WorldBoundary2DBehavior, { ... });\n * const hits = boundary.getLastHits(); // Fully typed!\n * ```\n */\nexport function defineBehavior<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n>(\n config: DefineBehaviorConfig<O, H, I>,\n): BehaviorDescriptor<O, H, I> {\n return {\n key: Symbol.for(`zylem:behavior:${config.name}`),\n defaultOptions: config.defaultOptions,\n systemFactory: config.systemFactory,\n createHandle: config.createHandle,\n };\n}\n","import { GameEntity } from '../entities/entity';\nimport { BehaviorDescriptor } from './behavior-descriptor';\n\n/**\n * Type-safe helper to apply a behavior to an entity and return the entity cast to the behavior's interface.\n * \n * @param entity The entity to apply the behavior to\n * @param descriptor The behavior descriptor\n * @param options Behavior options\n * @returns The entity, cast to E & I (where I is the behavior's interface)\n */\nexport function useBehavior<\n E extends GameEntity<any>,\n O extends Record<string, any>,\n H extends Record<string, any>,\n I // Entity Interface\n>(\n entity: E,\n descriptor: BehaviorDescriptor<O, H, I>,\n options?: Partial<O>\n): E & I {\n entity.use(descriptor, options);\n return entity as unknown as E & I;\n}\n","/**\n * Core ECS Components\n * \n * These are pure data interfaces with no logic.\n * They work alongside the existing bitecs components in transformable.system.ts\n */\n\nimport type { RigidBody } from '@dimforge/rapier3d-compat';\nimport { Vector3, Quaternion } from 'three';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// TransformComponent (render-facing)\n// Written only by PhysicsSyncBehavior, read by rendering/animation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface TransformComponent {\n\tposition: Vector3;\n\trotation: Quaternion;\n}\n\nexport function createTransformComponent(): TransformComponent {\n\treturn {\n\t\tposition: new Vector3(),\n\t\trotation: new Quaternion(),\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PhysicsBodyComponent (bridge to Rapier, not state)\n// ECS does not store velocity, mass, etc. - Rapier owns all physical truth\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PhysicsBodyComponent {\n\tbody: RigidBody;\n}\n\nexport function createPhysicsBodyComponent(body: RigidBody): PhysicsBodyComponent {\n\treturn { body };\n}\n","/**\n * PhysicsStepBehavior\n * \n * Single authoritative place where Rapier advances.\n * Runs after all force-producing behaviors.\n */\n\nimport type { World } from '@dimforge/rapier3d-compat';\nimport type { Behavior } from './thruster/thruster-movement.behavior';\n\n/**\n * PhysicsStepBehavior - Authoritative physics step\n * \n * This behavior is responsible for advancing the Rapier physics simulation.\n * It should run AFTER all force-producing behaviors (like ThrusterMovementBehavior).\n */\nexport class PhysicsStepBehavior implements Behavior {\n\tconstructor(private physicsWorld: World) {}\n\n\tupdate(dt: number): void {\n\t\tthis.physicsWorld.timestep = dt;\n\t\tthis.physicsWorld.step();\n\t}\n}\n","/**\n * PhysicsSyncBehavior\n * \n * Syncs physics state (position, rotation) from Rapier bodies to ECS TransformComponents.\n * This is what keeps Three.js honest - rendering reads from TransformComponent.\n */\n\nimport type { ZylemWorld } from '../collision/world';\nimport type { TransformComponent, PhysicsBodyComponent } from './components';\nimport type { Behavior } from './thruster/thruster-movement.behavior';\n\n/**\n * Entity with physics body and transform components\n */\ninterface PhysicsSyncEntity {\n\tphysics: PhysicsBodyComponent;\n\ttransform: TransformComponent;\n}\n\n/**\n * PhysicsSyncBehavior - Physics → ECS sync\n * \n * Responsibilities:\n * - Query entities with PhysicsBodyComponent and TransformComponent\n * - Copy position from body.translation() to transform.position\n * - Copy rotation from body.rotation() to transform.rotation\n * \n * This runs AFTER PhysicsStepBehavior, before rendering.\n */\nexport class PhysicsSyncBehavior implements Behavior {\n\tconstructor(private world: ZylemWorld) {}\n\n\t/**\n\t * Query entities that have both physics body and transform components\n\t */\n\tprivate queryEntities(): PhysicsSyncEntity[] {\n\t\tconst entities: PhysicsSyncEntity[] = [];\n\t\t\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\t\t\tif (gameEntity.physics?.body && gameEntity.transform) {\n\t\t\t\tentities.push({\n\t\t\t\t\tphysics: gameEntity.physics,\n\t\t\t\t\ttransform: gameEntity.transform,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn entities;\n\t}\n\n\tupdate(_dt: number): void {\n\t\tconst entities = this.queryEntities();\n\n\t\tfor (const e of entities) {\n\t\t\tconst body = e.physics.body;\n\t\t\tconst transform = e.transform;\n\n\t\t\t// Sync position\n\t\t\tconst p = body.translation();\n\t\t\ttransform.position.set(p.x, p.y, p.z);\n\n\t\t\t// Sync rotation\n\t\t\tconst r = body.rotation();\n\t\t\ttransform.rotation.set(r.x, r.y, r.z, r.w);\n\t\t}\n\t}\n}\n","/**\n * Thruster-specific ECS Components\n * \n * These components are specific to the thruster movement system.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterMovementComponent (capability / hardware spec)\n// Defines the thrust capabilities of an entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterMovementComponent {\n\t/** Linear thrust force in Newtons (or scaled units) */\n\tlinearThrust: number;\n\t/** Angular thrust torque scalar */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n\t/** Optional angular damping override */\n\tangularDamping?: number;\n}\n\nexport function createThrusterMovementComponent(\n\tlinearThrust: number,\n\tangularThrust: number,\n\toptions?: { linearDamping?: number; angularDamping?: number }\n): ThrusterMovementComponent {\n\treturn {\n\t\tlinearThrust,\n\t\tangularThrust,\n\t\tlinearDamping: options?.linearDamping,\n\t\tangularDamping: options?.angularDamping,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterInputComponent (intent)\n// Written by: Player controller, AI controller, FSM adapter\n// Read by: ThrusterMovementBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterInputComponent {\n\t/** Forward thrust intent: 0..1 */\n\tthrust: number;\n\t/** Rotation intent: -1..1 */\n\trotate: number;\n}\n\nexport function createThrusterInputComponent(): ThrusterInputComponent {\n\treturn {\n\t\tthrust: 0,\n\t\trotate: 0,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterStateComponent (optional but useful)\n// Useful for: damage, EMP, overheating, UI feedback\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterStateComponent {\n\t/** Whether the thruster is enabled */\n\tenabled: boolean;\n\t/** Current thrust after FSM/gating */\n\tcurrentThrust: number;\n}\n\nexport function createThrusterStateComponent(): ThrusterStateComponent {\n\treturn {\n\t\tenabled: true,\n\t\tcurrentThrust: 0,\n\t};\n}\n","/**\n * ThrusterFSM\n * \n * State machine controller for thruster behavior.\n * FSM does NOT touch physics or ThrusterMovementBehavior - it only writes ThrusterInputComponent.\n */\n\nimport { StateMachine, t, type ITransition } from 'typescript-fsm';\nimport type { ThrusterInputComponent } from './components';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterState {\n\tIdle = 'idle',\n\tActive = 'active',\n\tBoosting = 'boosting',\n\tDisabled = 'disabled',\n\tDocked = 'docked',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterEvent {\n\tActivate = 'activate',\n\tDeactivate = 'deactivate',\n\tBoost = 'boost',\n\tEndBoost = 'endBoost',\n\tDisable = 'disable',\n\tEnable = 'enable',\n\tDock = 'dock',\n\tUndock = 'undock',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Context Object\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterFSMContext {\n\tinput: ThrusterInputComponent;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Player Input (raw input from controller/keyboard)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PlayerInput {\n\tthrust: number;\n\trotate: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterFSM Controller\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ThrusterFSM {\n\tmachine: StateMachine<ThrusterState, ThrusterEvent, never>;\n\n\tconstructor(private ctx: ThrusterFSMContext) {\n\t\tthis.machine = new StateMachine<ThrusterState, ThrusterEvent, never>(\n\t\t\tThrusterState.Idle,\n\t\t\t[\n\t\t\t\t// Core transitions\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Boost, ThrusterState.Boosting),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Dock, ThrusterState.Docked),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.EndBoost, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Disabled, ThrusterEvent.Enable, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Docked, ThrusterEvent.Undock, ThrusterState.Idle),\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): ThrusterState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to transition state\n\t */\n\tdispatch(event: ThrusterEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.dispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM state based on player input.\n\t * Auto-transitions between Idle/Active to report current state.\n\t * Does NOT modify input - just observes and reports.\n\t */\n\tupdate(playerInput: PlayerInput): void {\n\t\tconst state = this.machine.getState();\n\t\tconst hasInput = Math.abs(playerInput.thrust) > 0.01 || Math.abs(playerInput.rotate) > 0.01;\n\n\t\t// Auto-transition to report state based on input\n\t\tif (hasInput && state === ThrusterState.Idle) {\n\t\t\tthis.dispatch(ThrusterEvent.Activate);\n\t\t} else if (!hasInput && state === ThrusterState.Active) {\n\t\t\tthis.dispatch(ThrusterEvent.Deactivate);\n\t\t}\n\t}\n}\n","/**\n * ThrusterMovementBehavior\n * \n * This is the heart of the thruster movement system - a pure, stateless force generator.\n * Works identically for player, AI, and replay.\n */\n\nimport type { ZylemWorld } from '../../collision/world';\nimport type { PhysicsBodyComponent } from '../components';\nimport type { ThrusterMovementComponent, ThrusterInputComponent } from './components';\n\n/**\n * Zylem-style Behavior interface\n */\nexport interface Behavior {\n\tupdate(dt: number): void;\n}\n\n/**\n * Entity with thruster components\n */\nexport interface ThrusterEntity {\n\tphysics: PhysicsBodyComponent;\n\tthruster: ThrusterMovementComponent;\n\t$thruster: ThrusterInputComponent;\n}\n\n/**\n * ThrusterMovementBehavior - Force generator for thruster-equipped entities\n * \n * Responsibilities:\n * - Query entities with PhysicsBody, ThrusterMovement, and ThrusterInput components\n * - Apply velocities based on thrust input (2D mode)\n * - Apply angular velocity based on rotation input\n */\nexport class ThrusterMovementBehavior implements Behavior {\n\tconstructor(private world: ZylemWorld) {}\n\n\t/**\n\t * Query function - returns entities with required thruster components\n\t */\n\tprivate queryEntities(): ThrusterEntity[] {\n\t\tconst entities: ThrusterEntity[] = [];\n\t\t\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\t\t\tif (\n\t\t\t\tgameEntity.physics?.body &&\n\t\t\t\tgameEntity.thruster &&\n\t\t\t\tgameEntity.$thruster\n\t\t\t) {\n\t\t\t\tentities.push({\n\t\t\t\t\tphysics: gameEntity.physics,\n\t\t\t\t\tthruster: gameEntity.thruster,\n\t\t\t\t\t$thruster: gameEntity.$thruster,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn entities;\n\t}\n\n\tupdate(_dt: number): void {\n\t\tconst entities = this.queryEntities();\n\n\t\tfor (const e of entities) {\n\t\t\tconst body = e.physics.body;\n\t\t\tconst thruster = e.thruster;\n\t\t\tconst input = e.$thruster;\n\n\t\t\t// Get Z rotation from quaternion (for 2D sprites)\n\t\t\tconst q = body.rotation();\n\t\t\tconst rotationZ = Math.atan2(2 * (q.w * q.z + q.x * q.y), 1 - 2 * (q.y * q.y + q.z * q.z));\n\n\t\t\t// --- Linear thrust (Asteroids-style: adds velocity in forward direction) ---\n\t\t\tif (input.thrust !== 0) {\n\t\t\t\tconst currentVel = body.linvel();\n\t\t\t\t\n\t\t\t\tif (input.thrust > 0) {\n\t\t\t\t\t// Forward thrust: add velocity in facing direction\n\t\t\t\t\tconst forwardX = Math.sin(-rotationZ);\n\t\t\t\t\tconst forwardY = Math.cos(-rotationZ);\n\t\t\t\t\tconst thrustAmount = thruster.linearThrust * input.thrust * 0.1;\n\t\t\t\t\tbody.setLinvel({\n\t\t\t\t\t\tx: currentVel.x + forwardX * thrustAmount,\n\t\t\t\t\t\ty: currentVel.y + forwardY * thrustAmount,\n\t\t\t\t\t\tz: currentVel.z\n\t\t\t\t\t}, true);\n\t\t\t\t} else {\n\t\t\t\t\t// Brake: reduce current velocity (slow down)\n\t\t\t\t\tconst brakeAmount = 0.9; // 10% reduction per frame\n\t\t\t\t\tbody.setLinvel({\n\t\t\t\t\t\tx: currentVel.x * brakeAmount,\n\t\t\t\t\t\ty: currentVel.y * brakeAmount,\n\t\t\t\t\t\tz: currentVel.z\n\t\t\t\t\t}, true);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// --- Angular thrust (Z-axis for 2D) ---\n\t\t\tif (input.rotate !== 0) {\n\t\t\t\tbody.setAngvel({ x: 0, y: 0, z: -thruster.angularThrust * input.rotate }, true);\n\t\t\t} else {\n\t\t\t\t// Stop rotation when no input\n\t\t\t\tconst angVel = body.angvel();\n\t\t\t\tbody.setAngvel({ x: angVel.x, y: angVel.y, z: 0 }, true);\n\t\t\t}\n\t\t}\n\t}\n}\n\n","/**\n * Thruster Behavior Descriptor\n * \n * Type-safe descriptor for the thruster behavior system using the new entity.use() API.\n * This wraps the existing ThrusterMovementBehavior and components.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\nimport { ThrusterMovementBehavior, ThrusterEntity } from './thruster-movement.behavior';\nimport { ThrusterFSM } from './thruster-fsm';\nimport type { ThrusterMovementComponent, ThrusterInputComponent } from './components';\n\n/**\n * Thruster behavior options (typed for entity.use() autocomplete)\n */\nexport interface ThrusterBehaviorOptions {\n\t/** Forward thrust force (default: 10) */\n\tlinearThrust: number;\n\t/** Rotation torque (default: 5) */\n\tangularThrust: number;\n}\n\nconst defaultOptions: ThrusterBehaviorOptions = {\n\tlinearThrust: 10,\n\tangularThrust: 5,\n};\n\n/**\n * Adapter that wraps ThrusterMovementBehavior as a BehaviorSystem.\n * \n * This bridges the entity.use() pattern with the ECS component-based approach:\n * - Reads options from entity behaviorRefs\n * - Initializes ThrusterMovementComponent and ThrusterInputComponent on entities\n * - Creates FSM lazily and attaches to BehaviorRef for access via handle.getFSM()\n * - Delegates physics to ThrusterMovementBehavior\n * \n * NOTE: Input is controlled by the user via entity.onUpdate() - set entity.input.thrust/rotate\n */\nclass ThrusterBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: ThrusterMovementBehavior;\n\n\tconstructor(private world: any) {\n\t\tthis.movementBehavior = new ThrusterMovementBehavior(world);\n\t}\n\n\tupdate(ecs: IWorld, delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\n\t\t// Initialize ECS components on entities with thruster behavior refs\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\t\t\t\n\t\t\tif (typeof gameEntity.getBehaviorRefs !== 'function') continue;\n\t\t\t\n\t\t\tconst refs = gameEntity.getBehaviorRefs();\n\t\t\tconst thrusterRef = refs.find((r: any) => \n\t\t\t\tr.descriptor.key === Symbol.for('zylem:behavior:thruster')\n\t\t\t);\n\t\t\t\n\t\t\tif (!thrusterRef || !gameEntity.body) continue;\n\n\t\t\tconst options = thrusterRef.options as ThrusterBehaviorOptions;\n\n\t\t\t// Ensure entity has thruster components (initialized once)\n\t\t\tif (!gameEntity.thruster) {\n\t\t\t\tgameEntity.thruster = {\n\t\t\t\t\tlinearThrust: options.linearThrust,\n\t\t\t\t\tangularThrust: options.angularThrust,\n\t\t\t\t} as ThrusterMovementComponent;\n\t\t\t}\n\n\t\t\tif (!gameEntity.$thruster) {\n\t\t\t\tgameEntity.$thruster = {\n\t\t\t\t\tthrust: 0,\n\t\t\t\t\trotate: 0,\n\t\t\t\t} as ThrusterInputComponent;\n\t\t\t}\n\n\t\t\tif (!gameEntity.physics) {\n\t\t\t\tgameEntity.physics = { body: gameEntity.body };\n\t\t\t}\n\n\t\t\t// Create FSM lazily and attach to the BehaviorRef for handle.getFSM()\n\t\t\tif (!thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm = new ThrusterFSM({ input: gameEntity.$thruster });\n\t\t\t}\n\n\t\t\t// Update FSM to sync state with input (auto-transitions)\n\t\t\tif (thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm.update({\n\t\t\t\t\tthrust: gameEntity.$thruster.thrust,\n\t\t\t\t\trotate: gameEntity.$thruster.rotate,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Delegate to the existing movement behavior\n\t\tthis.movementBehavior.update(delta);\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// Cleanup if needed\n\t}\n}\n\n/**\n * ThrusterBehavior - typed descriptor for thruster movement.\n * \n * Uses the existing ThrusterMovementBehavior under the hood.\n * \n * @example\n * ```typescript\n * import { ThrusterBehavior } from \"@zylem/game-lib\";\n * \n * const ship = createSprite({ ... });\n * ship.use(ThrusterBehavior, { linearThrust: 15, angularThrust: 8 });\n * ```\n */\nexport const ThrusterBehavior = defineBehavior<ThrusterBehaviorOptions, Record<string, never>, ThrusterEntity>({\n\tname: 'thruster',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new ThrusterBehaviorSystem(ctx.world),\n});\n","/**\n * ScreenWrapFSM\n * \n * State machine for screen wrap behavior.\n * Reports position relative to bounds edges.\n */\n\nimport { StateMachine, t } from 'typescript-fsm';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapState {\n\tCenter = 'center',\n\tNearEdgeLeft = 'near-edge-left',\n\tNearEdgeRight = 'near-edge-right',\n\tNearEdgeTop = 'near-edge-top',\n\tNearEdgeBottom = 'near-edge-bottom',\n\tWrapped = 'wrapped',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapEvent {\n\tEnterCenter = 'enter-center',\n\tApproachLeft = 'approach-left',\n\tApproachRight = 'approach-right',\n\tApproachTop = 'approach-top',\n\tApproachBottom = 'approach-bottom',\n\tWrap = 'wrap',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ScreenWrapFSM\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScreenWrapFSM {\n\tmachine: StateMachine<ScreenWrapState, ScreenWrapEvent, never>;\n\n\tconstructor() {\n\t\tthis.machine = new StateMachine<ScreenWrapState, ScreenWrapEvent, never>(\n\t\t\tScreenWrapState.Center,\n\t\t\t[\n\t\t\t\t// From Center\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t\t\n\t\t\t\t// From NearEdge to Wrapped\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\n\t\t\t\t// From NearEdge back to Center\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped back to Center\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped to NearEdge (landed near opposite edge)\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): ScreenWrapState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: ScreenWrapEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.dispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on entity position relative to bounds\n\t */\n\tupdate(position: { x: number; y: number }, bounds: {\n\t\tminX: number; maxX: number; minY: number; maxY: number;\n\t\tedgeThreshold: number;\n\t}, wrapped: boolean): void {\n\t\tconst { x, y } = position;\n\t\tconst { minX, maxX, minY, maxY, edgeThreshold } = bounds;\n\n\t\tif (wrapped) {\n\t\t\tthis.dispatch(ScreenWrapEvent.Wrap);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if near edges\n\t\tconst nearLeft = x < minX + edgeThreshold;\n\t\tconst nearRight = x > maxX - edgeThreshold;\n\t\tconst nearBottom = y < minY + edgeThreshold;\n\t\tconst nearTop = y > maxY - edgeThreshold;\n\n\t\tif (nearLeft) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachLeft);\n\t\t} else if (nearRight) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachRight);\n\t\t} else if (nearTop) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachTop);\n\t\t} else if (nearBottom) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachBottom);\n\t\t} else {\n\t\t\tthis.dispatch(ScreenWrapEvent.EnterCenter);\n\t\t}\n\t}\n}\n","/**\n * ScreenWrapBehavior\n * \n * When an entity exits the defined 2D bounds, it wraps around to the opposite edge.\n * Asteroids-style screen wrapping with FSM for edge detection.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\nimport { ScreenWrapFSM } from './screen-wrap-fsm';\n\n/**\n * Screen wrap options (typed for entity.use() autocomplete)\n */\nexport interface ScreenWrapOptions {\n\t/** Width of the wrapping area (default: 20) */\n\twidth: number;\n\t/** Height of the wrapping area (default: 15) */\n\theight: number;\n\t/** Center X position (default: 0) */\n\tcenterX: number;\n\t/** Center Y position (default: 0) */\n\tcenterY: number;\n\t/** Distance from edge to trigger NearEdge state (default: 2) */\n\tedgeThreshold: number;\n}\n\nconst defaultOptions: ScreenWrapOptions = {\n\twidth: 20,\n\theight: 15,\n\tcenterX: 0,\n\tcenterY: 0,\n\tedgeThreshold: 2,\n};\n\n/**\n * ScreenWrapSystem - Wraps entities around 2D bounds\n */\nclass ScreenWrapSystem implements BehaviorSystem {\n\tconstructor(private world: any) {}\n\n\tupdate(ecs: IWorld, delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\t\t\t\n\t\t\tif (typeof gameEntity.getBehaviorRefs !== 'function') continue;\n\t\t\t\n\t\t\tconst refs = gameEntity.getBehaviorRefs();\n\t\t\tconst wrapRef = refs.find((r: any) => \n\t\t\t\tr.descriptor.key === Symbol.for('zylem:behavior:screen-wrap')\n\t\t\t);\n\t\t\t\n\t\t\tif (!wrapRef || !gameEntity.body) continue;\n\n\t\t\tconst options = wrapRef.options as ScreenWrapOptions;\n\n\t\t\t// Create FSM lazily\n\t\t\tif (!wrapRef.fsm) {\n\t\t\t\twrapRef.fsm = new ScreenWrapFSM();\n\t\t\t}\n\n\t\t\tconst wrapped = this.wrapEntity(gameEntity, options);\n\n\t\t\t// Update FSM with position and wrap state\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tconst { width, height, centerX, centerY, edgeThreshold } = options;\n\t\t\tconst halfWidth = width / 2;\n\t\t\tconst halfHeight = height / 2;\n\n\t\t\twrapRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y },\n\t\t\t\t{\n\t\t\t\t\tminX: centerX - halfWidth,\n\t\t\t\t\tmaxX: centerX + halfWidth,\n\t\t\t\t\tminY: centerY - halfHeight,\n\t\t\t\t\tmaxY: centerY + halfHeight,\n\t\t\t\t\tedgeThreshold,\n\t\t\t\t},\n\t\t\t\twrapped\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate wrapEntity(entity: any, options: ScreenWrapOptions): boolean {\n\t\tconst body = entity.body;\n\t\tif (!body) return false;\n\n\t\tconst { width, height, centerX, centerY } = options;\n\t\tconst halfWidth = width / 2;\n\t\tconst halfHeight = height / 2;\n\n\t\tconst minX = centerX - halfWidth;\n\t\tconst maxX = centerX + halfWidth;\n\t\tconst minY = centerY - halfHeight;\n\t\tconst maxY = centerY + halfHeight;\n\n\t\tconst pos = body.translation();\n\t\tlet newX = pos.x;\n\t\tlet newY = pos.y;\n\t\tlet wrapped = false;\n\n\t\t// Wrap X\n\t\tif (pos.x < minX) {\n\t\t\tnewX = maxX - (minX - pos.x);\n\t\t\twrapped = true;\n\t\t} else if (pos.x > maxX) {\n\t\t\tnewX = minX + (pos.x - maxX);\n\t\t\twrapped = true;\n\t\t}\n\n\t\t// Wrap Y\n\t\tif (pos.y < minY) {\n\t\t\tnewY = maxY - (minY - pos.y);\n\t\t\twrapped = true;\n\t\t} else if (pos.y > maxY) {\n\t\t\tnewY = minY + (pos.y - maxY);\n\t\t\twrapped = true;\n\t\t}\n\n\t\tif (wrapped) {\n\t\t\tbody.setTranslation({ x: newX, y: newY, z: pos.z }, true);\n\t\t}\n\n\t\treturn wrapped;\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// Cleanup if needed\n\t}\n}\n\n/**\n * ScreenWrapBehavior - Wraps entities around 2D bounds\n * \n * @example\n * ```typescript\n * import { ScreenWrapBehavior } from \"@zylem/game-lib\";\n * \n * const ship = createSprite({ ... });\n * const wrapRef = ship.use(ScreenWrapBehavior, { width: 20, height: 15 });\n * \n * // Access FSM to observe edge state\n * const fsm = wrapRef.getFSM();\n * console.log(fsm?.getState()); // 'center', 'near-edge-left', 'wrapped', etc.\n * ```\n */\nexport const ScreenWrapBehavior = defineBehavior({\n\tname: 'screen-wrap',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new ScreenWrapSystem(ctx.world),\n});\n\n","/**\n * WorldBoundary2DFSM\n *\n * Minimal FSM + extended state to track which world boundaries were hit.\n *\n * Notes:\n * - \"Hit boundaries\" is inherently a *set* (can hit left+bottom in one frame),\n * so we store it as extended state (`lastHits`) rather than a single FSM state.\n * - The FSM state is still useful for coarse status like \"inside\" vs \"touching\".\n */\n\nimport { StateMachine, t } from 'typescript-fsm';\n\nexport type WorldBoundary2DHit = 'top' | 'bottom' | 'left' | 'right';\nexport type WorldBoundary2DHits = Record<WorldBoundary2DHit, boolean>;\n\nexport interface WorldBoundary2DPosition {\n\tx: number;\n\ty: number;\n}\n\nexport interface WorldBoundary2DBounds {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n}\n\nexport enum WorldBoundary2DState {\n\tInside = 'inside',\n\tTouching = 'touching',\n}\n\nexport enum WorldBoundary2DEvent {\n\tEnterInside = 'enter-inside',\n\tTouchBoundary = 'touch-boundary',\n}\n\n/**\n * Compute which boundaries are being hit for a position and bounds.\n * This matches the semantics of the legacy `boundary2d` behavior:\n * - left hit if x <= left\n * - right hit if x >= right\n * - bottom hit if y <= bottom\n * - top hit if y >= top\n */\nexport function computeWorldBoundary2DHits(\n\tposition: WorldBoundary2DPosition,\n\tbounds: WorldBoundary2DBounds\n): WorldBoundary2DHits {\n\tconst hits: WorldBoundary2DHits = {\n\t\ttop: false,\n\t\tbottom: false,\n\t\tleft: false,\n\t\tright: false,\n\t};\n\n\tif (position.x <= bounds.left) hits.left = true;\n\telse if (position.x >= bounds.right) hits.right = true;\n\n\tif (position.y <= bounds.bottom) hits.bottom = true;\n\telse if (position.y >= bounds.top) hits.top = true;\n\n\treturn hits;\n}\n\nexport function hasAnyWorldBoundary2DHit(hits: WorldBoundary2DHits): boolean {\n\treturn !!(hits.left || hits.right || hits.top || hits.bottom);\n}\n\n/**\n * FSM wrapper with \"extended state\" (lastHits / lastPosition).\n * Systems should call `update(...)` once per frame.\n */\nexport class WorldBoundary2DFSM {\n\tpublic readonly machine: StateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>;\n\n\tprivate lastHits: WorldBoundary2DHits = { top: false, bottom: false, left: false, right: false };\n\tprivate lastPosition: WorldBoundary2DPosition | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new StateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>(\n\t\t\tWorldBoundary2DState.Inside,\n\t\t\t[\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): WorldBoundary2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed hits (always available after first update call).\n\t */\n\tgetLastHits(): WorldBoundary2DHits {\n\t\treturn this.lastHits;\n\t}\n\n\t/**\n\t * Returns adjusted movement values based on boundary hits.\n\t * If the entity is touching a boundary and trying to move further into it,\n\t * that axis component is zeroed out.\n\t *\n\t * @param moveX - The desired X movement\n\t * @param moveY - The desired Y movement\n\t * @returns Adjusted { moveX, moveY } with boundary-blocked axes zeroed\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number } {\n\t\tconst hits = this.lastHits;\n\n\t\tlet adjustedX = moveX;\n\t\tlet adjustedY = moveY;\n\n\t\t// If moving left and hitting left, or moving right and hitting right, zero X\n\t\tif ((hits.left && moveX < 0) || (hits.right && moveX > 0)) {\n\t\t\tadjustedX = 0;\n\t\t}\n\n\t\t// If moving down and hitting bottom, or moving up and hitting top, zero Y\n\t\tif ((hits.bottom && moveY < 0) || (hits.top && moveY > 0)) {\n\t\t\tadjustedY = 0;\n\t\t}\n\n\t\treturn { moveX: adjustedX, moveY: adjustedY };\n\t}\n\n\t/**\n\t * Returns the last position passed to `update`, if any.\n\t */\n\tgetLastPosition(): WorldBoundary2DPosition | null {\n\t\treturn this.lastPosition;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last `update(...)` call.\n\t * This is optional metadata; systems can ignore it.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Update FSM + extended state based on current position and bounds.\n\t * Returns the computed hits for convenience.\n\t */\n\tupdate(position: WorldBoundary2DPosition, bounds: WorldBoundary2DBounds): WorldBoundary2DHits {\n\t\tconst hits = computeWorldBoundary2DHits(position, bounds);\n\n\t\tthis.lastHits = hits;\n\t\tthis.lastPosition = { x: position.x, y: position.y };\n\t\tthis.lastUpdatedAtMs = Date.now();\n\n\t\tif (hasAnyWorldBoundary2DHit(hits)) {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.TouchBoundary);\n\t\t} else {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.EnterInside);\n\t\t}\n\n\t\treturn hits;\n\t}\n\n\tprivate dispatch(event: WorldBoundary2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.dispatch(event);\n\t\t}\n\t}\n}\n","/**\n * WorldBoundary2DBehavior\n *\n * Tracks which 2D world boundaries an entity is touching.\n * Use `getMovement()` on the behavior handle to adjust movement based on hits.\n *\n * Source of truth: `entity.body` (Rapier rigid body), consistent with other new behaviors.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\nimport {\n\tWorldBoundary2DFSM,\n\ttype WorldBoundary2DBounds,\n\ttype WorldBoundary2DHits,\n} from './world-boundary-2d-fsm';\n\nexport interface WorldBoundary2DOptions {\n\t/**\n\t * World boundaries (in world units).\n\t * - left hit if x <= left\n\t * - right hit if x >= right\n\t * - bottom hit if y <= bottom\n\t * - top hit if y >= top\n\t */\n\tboundaries: WorldBoundary2DBounds;\n}\n\n/**\n * Handle methods provided by WorldBoundary2DBehavior\n */\nexport interface WorldBoundary2DHandle {\n\t/**\n\t * Get the last computed boundary hits.\n\t * Returns null until entity is spawned and FSM is initialized.\n\t */\n\tgetLastHits(): WorldBoundary2DHits | null;\n\n\t/**\n\t * Get adjusted movement values based on boundary hits.\n\t * Zeros out movement into boundaries the entity is touching.\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number };\n}\n\nconst defaultOptions: WorldBoundary2DOptions = {\n\tboundaries: { top: 0, bottom: 0, left: 0, right: 0 },\n};\n\n/**\n * Creates behavior-specific handle methods for WorldBoundary2DBehavior.\n */\nfunction createWorldBoundary2DHandle(\n\tref: BehaviorRef<WorldBoundary2DOptions>\n): WorldBoundary2DHandle {\n\treturn {\n\t\tgetLastHits: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getLastHits() ?? null;\n\t\t},\n\t\tgetMovement: (moveX: number, moveY: number) => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getMovement(moveX, moveY) ?? { moveX, moveY };\n\t\t},\n\t};\n}\n\n/**\n * WorldBoundary2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - computes and tracks boundary hits using the FSM\n */\nclass WorldBoundary2DSystem implements BehaviorSystem {\n\tconstructor(private world: any) {}\n\n\tupdate(_ecs: IWorld, _delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\n\t\t\tif (typeof gameEntity.getBehaviorRefs !== 'function') continue;\n\n\t\t\tconst refs = gameEntity.getBehaviorRefs();\n\t\t\tconst boundaryRef = refs.find(\n\t\t\t\t(r: any) => r.descriptor.key === Symbol.for('zylem:behavior:world-boundary-2d')\n\t\t\t);\n\n\t\t\tif (!boundaryRef || !gameEntity.body) continue;\n\n\t\t\tconst options = boundaryRef.options as WorldBoundary2DOptions;\n\n\t\t\t// Create FSM lazily on first update after spawn\n\t\t\tif (!boundaryRef.fsm) {\n\t\t\t\tboundaryRef.fsm = new WorldBoundary2DFSM();\n\t\t\t}\n\n\t\t\tconst body = gameEntity.body;\n\t\t\tconst pos = body.translation();\n\n\t\t\t// Update FSM with current position - consumers use getMovement() to act on hits\n\t\t\tboundaryRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y },\n\t\t\t\toptions.boundaries\n\t\t\t);\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// No explicit cleanup required (FSMs are stored on behavior refs)\n\t}\n}\n\n/**\n * WorldBoundary2DBehavior\n *\n * @example\n * ```ts\n * import { WorldBoundary2DBehavior } from \"@zylem/game-lib\";\n *\n * const ship = createSprite({ ... });\n * const boundary = ship.use(WorldBoundary2DBehavior, {\n * boundaries: { left: -10, right: 10, bottom: -7.5, top: 7.5 },\n * });\n *\n * ship.onUpdate(({ me }) => {\n * let moveX = ..., moveY = ...;\n * const hits = boundary.getLastHits(); // Fully typed!\n * ({ moveX, moveY } = boundary.getMovement(moveX, moveY));\n * me.moveXY(moveX, moveY);\n * });\n * ```\n */\nexport const WorldBoundary2DBehavior = defineBehavior({\n\tname: 'world-boundary-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new WorldBoundary2DSystem(ctx.world),\n\tcreateHandle: createWorldBoundary2DHandle,\n});\n","/**\n * Ricochet2DFSM\n *\n * FSM + extended state to track ricochet events and results.\n * The FSM state tracks whether a ricochet is currently occurring.\n */\nimport { BaseEntityInterface } from \"../../types/entity-types\";\nimport { StateMachine, t } from 'typescript-fsm';\n\nexport interface Ricochet2DResult {\n\t/** The reflected velocity vector */\n\tvelocity: { x: number; y: number; z?: number };\n\t/** The resulting speed after reflection */\n\tspeed: number;\n\t/** The collision normal used for reflection */\n\tnormal: { x: number; y: number; z?: number };\n}\n\nexport interface Ricochet2DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\t/** Current velocity of the entity (optional if entity is provided) */\n\tselfVelocity?: { x: number; y: number; z?: number };\n\t/** Contact information from the collision */\n\tcontact: {\n\t\t/** The collision normal */\n\t\tnormal?: { x: number; y: number; z?: number };\n\t\t/**\n\t\t * Optional position where the collision occurred.\n\t\t * If provided, used for precise offset calculation.\n\t\t */\n\t\tposition?: { x: number; y: number; z?: number };\n\t};\n\t/**\n\t * Optional position of the entity that owns this behavior.\n\t * Used with contact.position for offset calculations.\n\t */\n\tselfPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional position of the other entity in the collision.\n\t * Used for paddle-style deflection: offset = (contactY - otherY) / halfHeight.\n\t */\n\totherPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional size of the other entity (e.g., paddle size).\n\t * If provided, used to normalize the offset based on the collision face.\n\t */\n\totherSize?: { x: number; y: number; z?: number };\n}\n\nexport enum Ricochet2DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet2DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\n/**\n * FSM wrapper with extended state (lastResult).\n * Systems or consumers call `computeRicochet(...)` when a collision occurs.\n */\nexport class Ricochet2DFSM {\n\tpublic readonly machine: StateMachine<Ricochet2DState, Ricochet2DEvent, never>;\n\n\tprivate lastResult: Ricochet2DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new StateMachine<Ricochet2DState, Ricochet2DEvent, never>(\n\t\t\tRicochet2DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): Ricochet2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last computation.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Compute a ricochet result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t */\n\tcomputeRicochet(\n\t\tctx: Ricochet2DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {}\n\t): Ricochet2DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 60,\n\t\t} = options;\n\n\t\t// Extract data from entities if provided\n\t\tconst { selfVelocity, selfPosition, otherPosition, otherSize } = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = Math.hypot(selfVelocity.x, selfVelocity.y);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute or extract collision normal\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute basic reflection\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, normal);\n\n\t\t// Apply angled deflection if requested\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\tselfVelocity,\n\t\t\t\tnormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position\n\t\t\t);\n\t\t}\n\n\t\t// Apply final speed constraints\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet2DResult = {\n\t\t\tvelocity: { x: reflected.x, y: reflected.y, z: 0 },\n\t\t\tspeed: Math.hypot(reflected.x, reflected.y),\n\t\t\tnormal: { x: normal.x, y: normal.y, z: 0 },\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = Date.now();\n\t\tthis.dispatch(Ricochet2DEvent.StartRicochet);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Extract velocity, position, and size data from entities or context.\n\t */\n\tprivate extractDataFromEntities(ctx: Ricochet2DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tif (ctx.entity?.body) {\n\t\t\tconst vel = ctx.entity.body.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };\n\t\t\tconst pos = ctx.entity.body.translation();\n\t\t\tselfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity?.body) {\n\t\t\tconst pos = ctx.otherEntity.body.translation();\n\t\t\totherPosition = otherPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\t/**\n\t * Compute collision normal from entity positions using AABB heuristic.\n\t */\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number; z?: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst dx = selfPosition.x - otherPosition.x;\n\t\tconst dy = selfPosition.y - otherPosition.y;\n\n\t\t// Simple \"which face was hit\" logic for box collisions\n\t\tif (Math.abs(dx) > Math.abs(dy)) {\n\t\t\treturn { x: dx > 0 ? 1 : -1, y: 0, z: 0 };\n\t\t} else {\n\t\t\treturn { x: 0, y: dy > 0 ? 1 : -1, z: 0 };\n\t\t}\n\t}\n\n\t/**\n\t * Compute basic reflection using the formula: v' = v - 2(v·n)n\n\t */\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst vx = velocity.x;\n\t\tconst vy = velocity.y;\n\t\tconst dotProduct = vx * normal.x + vy * normal.y;\n\n\t\treturn {\n\t\t\tx: vx - 2 * dotProduct * normal.x,\n\t\t\ty: vy - 2 * dotProduct * normal.y,\n\t\t};\n\t}\n\n\t/**\n\t * Compute angled deflection for paddle-style reflections.\n\t */\n\tprivate computeAngledDeflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\n\t\t// Tangent vector (perpendicular to normal)\n\t\tlet tx = -normal.y;\n\t\tlet ty = normal.x;\n\n\t\t// Ensure tangent points in positive direction of the deflection axis\n\t\t// so that positive offset (right/top) results in positive deflection\n\t\tif (Math.abs(normal.x) > Math.abs(normal.y)) {\n\t\t\t// Vertical face (Normal is X-aligned). Deflection axis is Y.\n\t\t\t// We want ty > 0.\n\t\t\tif (ty < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t} else {\n\t\t\t// Horizontal face (Normal is Y-aligned). Deflection axis is X.\n\t\t\t// We want tx > 0.\n\t\t\tif (tx < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t}\n\n\t\t// Compute offset based on hit position\n\t\tconst offset = this.computeHitOffset(\n\t\t\tvelocity,\n\t\t\tnormal,\n\t\t\tspeed,\n\t\t\ttx,\n\t\t\tty,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition\n\t\t);\n\n\t\tconst angle = clamp(offset, -1, 1) * maxAngleRad;\n\n\t\tconst cosA = Math.cos(angle);\n\t\tconst sinA = Math.sin(angle);\n\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: newSpeed * (normal.x * cosA + tx * sinA),\n\t\t\ty: newSpeed * (normal.y * cosA + ty * sinA),\n\t\t};\n\t}\n\n\t/**\n\t * Compute hit offset for angled deflection (-1 to 1).\n\t */\n\tprivate computeHitOffset(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\ttx: number,\n\t\tty: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): number {\n\t\t// Use position-based offset if available\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst useY = Math.abs(normal.x) > Math.abs(normal.y);\n\t\t\tconst halfExtent = useY ? otherSize.y / 2 : otherSize.x / 2;\n\n\t\t\tif (useY) {\n\t\t\t\tconst selfY = selfPosition?.y ?? contactPosition?.y ?? 0;\n\t\t\t\tconst paddleY = otherPosition.y;\n\t\t\t\treturn (selfY - paddleY) / halfExtent;\n\t\t\t} else {\n\t\t\t\tconst selfX = selfPosition?.x ?? contactPosition?.x ?? 0;\n\t\t\t\tconst paddleX = otherPosition.x;\n\t\t\t\treturn (selfX - paddleX) / halfExtent;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: use velocity-based offset\n\t\treturn (velocity.x * tx + velocity.y * ty) / speed;\n\t}\n\n\t/**\n\t * Apply speed constraints to the reflected velocity.\n\t */\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number\n\t): { x: number; y: number } {\n\t\tconst currentSpeed = Math.hypot(velocity.x, velocity.y);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t};\n\t}\n\n\t/**\n\t * Clear the ricochet state (call after consumer has applied the result).\n\t */\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t}\n\n\tprivate dispatch(event: Ricochet2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.dispatch(event);\n\t\t}\n\t}\n}\n","/**\n * Ricochet2DBehavior\n *\n * Computes 2D ricochet/reflection results for entities during collisions.\n * The behavior computes the result; the consumer decides how to apply it.\n *\n * Use `getRicochet(ctx)` on the behavior handle to compute reflection results.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\nimport { Ricochet2DFSM, type Ricochet2DResult, type Ricochet2DCollisionContext } from './ricochet-2d-fsm';\nexport type { Ricochet2DResult };\n\nexport interface Ricochet2DOptions {\n\t/**\n\t * Minimum speed after reflection.\n\t * Default: 2\n\t */\n\tminSpeed: number;\n\n\t/**\n\t * Maximum speed after reflection.\n\t * Default: 20\n\t */\n\tmaxSpeed: number;\n\n\t/**\n\t * Speed multiplier applied during angled reflection.\n\t * Default: 1.05\n\t */\n\tspeedMultiplier: number;\n\n\t/**\n\t * Reflection mode:\n\t * - 'simple': Basic axis inversion\n\t * - 'angled': Paddle-style deflection based on contact point\n\t * Default: 'angled'\n\t */\n\treflectionMode: 'simple' | 'angled';\n\n\t/**\n\t * Maximum deflection angle in degrees for angled mode.\n\t * Default: 60\n\t */\n\tmaxAngleDeg: number;\n}\n\n/**\n * Handle methods provided by Ricochet2DBehavior\n */\nexport interface Ricochet2DHandle {\n\t/**\n\t * Compute a ricochet/reflection result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t *\n\t * @param ctx - Collision context with selfVelocity and contact normal\n\t * @returns Ricochet result with velocity, speed, and normal, or null\n\t */\n\tgetRicochet(ctx: Ricochet2DCollisionContext): Ricochet2DResult | null;\n\n\t/**\n\t * Get the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null;\n}\n\nconst defaultOptions: Ricochet2DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 60,\n};\n\n/**\n * Creates behavior-specific handle methods for Ricochet2DBehavior.\n */\nfunction createRicochet2DHandle(\n\tref: BehaviorRef<Ricochet2DOptions>\n): Ricochet2DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet2DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t};\n}\n\n/**\n * Ricochet2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - lazily creates FSM instances for each entity\n *\n * Note: This behavior is consumer-driven. The system only manages FSM lifecycle.\n * Consumers call `getRicochet(ctx)` during collision callbacks to compute results.\n */\nclass Ricochet2DSystem implements BehaviorSystem {\n\tconstructor(private world: any) {}\n\n\tupdate(_ecs: IWorld, _delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\n\t\t\tif (typeof gameEntity.getBehaviorRefs !== 'function') continue;\n\n\t\t\tconst refs = gameEntity.getBehaviorRefs();\n\t\t\tconst ricochetRef = refs.find(\n\t\t\t\t(r: any) => r.descriptor.key === Symbol.for('zylem:behavior:ricochet-2d')\n\t\t\t);\n\n\t\t\tif (!ricochetRef) continue;\n\n\t\t\t// Create FSM lazily on first update after spawn\n\t\t\tif (!ricochetRef.fsm) {\n\t\t\t\tricochetRef.fsm = new Ricochet2DFSM();\n\t\t\t}\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// No explicit cleanup required (FSMs are stored on behavior refs)\n\t}\n}\n\n/**\n * Ricochet2DBehavior\n *\n * @example\n * ```ts\n * import { Ricochet2DBehavior } from \"@zylem/game-lib\";\n *\n * const ball = createSphere({ ... });\n * const ricochet = ball.use(Ricochet2DBehavior, {\n * minSpeed: 3,\n * maxSpeed: 15,\n * reflectionMode: 'angled',\n * });\n *\n * ball.onCollision(({ entity, other }) => {\n * const velocity = entity.body.linvel();\n * const result = ricochet.getRicochet({\n * selfVelocity: velocity,\n * contact: { normal: { x: 1, y: 0 } }, // from collision data\n * });\n *\n * if (result) {\n * entity.body.setLinvel(result.velocity, true);\n * }\n * });\n * ```\n */\nexport const Ricochet2DBehavior = defineBehavior({\n\tname: 'ricochet-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new Ricochet2DSystem(ctx.world),\n\tcreateHandle: createRicochet2DHandle,\n});\n","/**\n * MovementSequence2DFSM\n *\n * FSM + extended state to manage timed movement sequences.\n * Tracks current step, time remaining, and computes movement for consumer.\n */\n\nimport { StateMachine, t } from 'typescript-fsm';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface MovementSequence2DStep {\n\t/** Identifier for this step */\n\tname: string;\n\t/** X velocity for this step */\n\tmoveX?: number;\n\t/** Y velocity for this step */\n\tmoveY?: number;\n\t/** Duration in seconds */\n\ttimeInSeconds: number;\n}\n\nexport interface MovementSequence2DMovement {\n\tmoveX: number;\n\tmoveY: number;\n}\n\nexport interface MovementSequence2DProgress {\n\tstepIndex: number;\n\ttotalSteps: number;\n\tstepTimeRemaining: number;\n\tdone: boolean;\n}\n\nexport interface MovementSequence2DCurrentStep {\n\tname: string;\n\tindex: number;\n\tmoveX: number;\n\tmoveY: number;\n\ttimeRemaining: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum MovementSequence2DState {\n\tIdle = 'idle',\n\tRunning = 'running',\n\tPaused = 'paused',\n\tCompleted = 'completed',\n}\n\nexport enum MovementSequence2DEvent {\n\tStart = 'start',\n\tPause = 'pause',\n\tResume = 'resume',\n\tComplete = 'complete',\n\tReset = 'reset',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Implementation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class MovementSequence2DFSM {\n\tpublic readonly machine: StateMachine<MovementSequence2DState, MovementSequence2DEvent, never>;\n\n\tprivate sequence: MovementSequence2DStep[] = [];\n\tprivate loop: boolean = true;\n\tprivate currentIndex: number = 0;\n\tprivate timeRemaining: number = 0;\n\n\tconstructor() {\n\t\tthis.machine = new StateMachine<MovementSequence2DState, MovementSequence2DEvent, never>(\n\t\t\tMovementSequence2DState.Idle,\n\t\t\t[\n\t\t\t\t// From Idle\n\t\t\t\tt(MovementSequence2DState.Idle, MovementSequence2DEvent.Start, MovementSequence2DState.Running),\n\n\t\t\t\t// From Running\n\t\t\t\tt(MovementSequence2DState.Running, MovementSequence2DEvent.Pause, MovementSequence2DState.Paused),\n\t\t\t\tt(MovementSequence2DState.Running, MovementSequence2DEvent.Complete, MovementSequence2DState.Completed),\n\t\t\t\tt(MovementSequence2DState.Running, MovementSequence2DEvent.Reset, MovementSequence2DState.Idle),\n\n\t\t\t\t// From Paused\n\t\t\t\tt(MovementSequence2DState.Paused, MovementSequence2DEvent.Resume, MovementSequence2DState.Running),\n\t\t\t\tt(MovementSequence2DState.Paused, MovementSequence2DEvent.Reset, MovementSequence2DState.Idle),\n\n\t\t\t\t// From Completed\n\t\t\t\tt(MovementSequence2DState.Completed, MovementSequence2DEvent.Reset, MovementSequence2DState.Idle),\n\t\t\t\tt(MovementSequence2DState.Completed, MovementSequence2DEvent.Start, MovementSequence2DState.Running),\n\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(MovementSequence2DState.Idle, MovementSequence2DEvent.Pause, MovementSequence2DState.Idle),\n\t\t\t\tt(MovementSequence2DState.Idle, MovementSequence2DEvent.Resume, MovementSequence2DState.Idle),\n\t\t\t\tt(MovementSequence2DState.Running, MovementSequence2DEvent.Start, MovementSequence2DState.Running),\n\t\t\t\tt(MovementSequence2DState.Running, MovementSequence2DEvent.Resume, MovementSequence2DState.Running),\n\t\t\t\tt(MovementSequence2DState.Paused, MovementSequence2DEvent.Pause, MovementSequence2DState.Paused),\n\t\t\t\tt(MovementSequence2DState.Completed, MovementSequence2DEvent.Complete, MovementSequence2DState.Completed),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Initialize the sequence. Call this once with options.\n\t */\n\tinit(sequence: MovementSequence2DStep[], loop: boolean): void {\n\t\tthis.sequence = sequence;\n\t\tthis.loop = loop;\n\t\tthis.currentIndex = 0;\n\t\tthis.timeRemaining = sequence.length > 0 ? sequence[0].timeInSeconds : 0;\n\t}\n\n\tgetState(): MovementSequence2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Start the sequence (from Idle or Completed).\n\t */\n\tstart(): void {\n\t\tif (this.machine.getState() === MovementSequence2DState.Idle ||\n\t\t this.machine.getState() === MovementSequence2DState.Completed) {\n\t\t\tthis.currentIndex = 0;\n\t\t\tthis.timeRemaining = this.sequence.length > 0 ? this.sequence[0].timeInSeconds : 0;\n\t\t}\n\t\tthis.dispatch(MovementSequence2DEvent.Start);\n\t}\n\n\t/**\n\t * Pause the sequence.\n\t */\n\tpause(): void {\n\t\tthis.dispatch(MovementSequence2DEvent.Pause);\n\t}\n\n\t/**\n\t * Resume a paused sequence.\n\t */\n\tresume(): void {\n\t\tthis.dispatch(MovementSequence2DEvent.Resume);\n\t}\n\n\t/**\n\t * Reset to Idle state.\n\t */\n\treset(): void {\n\t\tthis.dispatch(MovementSequence2DEvent.Reset);\n\t\tthis.currentIndex = 0;\n\t\tthis.timeRemaining = this.sequence.length > 0 ? this.sequence[0].timeInSeconds : 0;\n\t}\n\n\t/**\n\t * Update the sequence with delta time.\n\t * Returns the current movement to apply.\n\t * Automatically starts if in Idle state.\n\t */\n\tupdate(delta: number): MovementSequence2DMovement {\n\t\tif (this.sequence.length === 0) {\n\t\t\treturn { moveX: 0, moveY: 0 };\n\t\t}\n\n\t\t// Auto-start if idle\n\t\tif (this.machine.getState() === MovementSequence2DState.Idle) {\n\t\t\tthis.start();\n\t\t}\n\n\t\t// Don't advance if paused or completed\n\t\tif (this.machine.getState() !== MovementSequence2DState.Running) {\n\t\t\tif (this.machine.getState() === MovementSequence2DState.Completed) {\n\t\t\t\treturn { moveX: 0, moveY: 0 };\n\t\t\t}\n\t\t\t// Paused - return current step's movement (but don't advance time)\n\t\t\tconst step = this.sequence[this.currentIndex];\n\t\t\treturn { moveX: step?.moveX ?? 0, moveY: step?.moveY ?? 0 };\n\t\t}\n\n\t\t// Advance time\n\t\tlet timeLeft = this.timeRemaining - delta;\n\n\t\t// Handle step transitions\n\t\twhile (timeLeft <= 0) {\n\t\t\tconst overflow = -timeLeft;\n\t\t\tthis.currentIndex += 1;\n\n\t\t\tif (this.currentIndex >= this.sequence.length) {\n\t\t\t\tif (!this.loop) {\n\t\t\t\t\tthis.dispatch(MovementSequence2DEvent.Complete);\n\t\t\t\t\treturn { moveX: 0, moveY: 0 };\n\t\t\t\t}\n\t\t\t\tthis.currentIndex = 0;\n\t\t\t}\n\n\t\t\ttimeLeft = this.sequence[this.currentIndex].timeInSeconds - overflow;\n\t\t}\n\n\t\tthis.timeRemaining = timeLeft;\n\n\t\tconst step = this.sequence[this.currentIndex];\n\t\treturn { moveX: step?.moveX ?? 0, moveY: step?.moveY ?? 0 };\n\t}\n\n\t/**\n\t * Get the current movement without advancing time.\n\t */\n\tgetMovement(): MovementSequence2DMovement {\n\t\tif (this.sequence.length === 0 ||\n\t\t this.machine.getState() === MovementSequence2DState.Completed) {\n\t\t\treturn { moveX: 0, moveY: 0 };\n\t\t}\n\t\tconst step = this.sequence[this.currentIndex];\n\t\treturn { moveX: step?.moveX ?? 0, moveY: step?.moveY ?? 0 };\n\t}\n\n\t/**\n\t * Get current step info.\n\t */\n\tgetCurrentStep(): MovementSequence2DCurrentStep | null {\n\t\tif (this.sequence.length === 0) return null;\n\t\tconst step = this.sequence[this.currentIndex];\n\t\tif (!step) return null;\n\t\treturn {\n\t\t\tname: step.name,\n\t\t\tindex: this.currentIndex,\n\t\t\tmoveX: step.moveX ?? 0,\n\t\t\tmoveY: step.moveY ?? 0,\n\t\t\ttimeRemaining: this.timeRemaining,\n\t\t};\n\t}\n\n\t/**\n\t * Get sequence progress.\n\t */\n\tgetProgress(): MovementSequence2DProgress {\n\t\treturn {\n\t\t\tstepIndex: this.currentIndex,\n\t\t\ttotalSteps: this.sequence.length,\n\t\t\tstepTimeRemaining: this.timeRemaining,\n\t\t\tdone: this.machine.getState() === MovementSequence2DState.Completed,\n\t\t};\n\t}\n\n\tprivate dispatch(event: MovementSequence2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.dispatch(event);\n\t\t}\n\t}\n}\n","/**\n * MovementSequence2DBehavior\n *\n * Sequences 2D movements over time. Each step defines velocity and duration.\n * The behavior computes movement; the consumer applies it.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorSystem } from '../behavior-system';\nimport {\n\tMovementSequence2DFSM,\n\ttype MovementSequence2DStep,\n\ttype MovementSequence2DMovement,\n\ttype MovementSequence2DCurrentStep,\n\ttype MovementSequence2DProgress,\n} from './movement-sequence-2d-fsm';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface MovementSequence2DOptions {\n\t/**\n\t * The sequence of movement steps.\n\t * Each step has name, moveX, moveY, and timeInSeconds.\n\t */\n\tsequence: MovementSequence2DStep[];\n\n\t/**\n\t * Whether to loop when the sequence ends.\n\t * Default: true\n\t */\n\tloop: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Handle methods provided by MovementSequence2DBehavior\n */\nexport interface MovementSequence2DHandle {\n\t/**\n\t * Get the current movement velocity.\n\t * Returns { moveX: 0, moveY: 0 } if sequence is empty or completed.\n\t */\n\tgetMovement(): MovementSequence2DMovement;\n\n\t/**\n\t * Get the current step info.\n\t * Returns null if sequence is empty.\n\t */\n\tgetCurrentStep(): MovementSequence2DCurrentStep | null;\n\n\t/**\n\t * Get sequence progress.\n\t */\n\tgetProgress(): MovementSequence2DProgress;\n\n\t/**\n\t * Pause the sequence. Movement values remain but time doesn't advance.\n\t */\n\tpause(): void;\n\n\t/**\n\t * Resume a paused sequence.\n\t */\n\tresume(): void;\n\n\t/**\n\t * Reset the sequence to the beginning.\n\t */\n\treset(): void;\n}\n\nconst defaultOptions: MovementSequence2DOptions = {\n\tsequence: [],\n\tloop: true,\n};\n\n/**\n * Creates behavior-specific handle methods for MovementSequence2DBehavior.\n */\nfunction createMovementSequence2DHandle(\n\tref: BehaviorRef<MovementSequence2DOptions>\n): MovementSequence2DHandle {\n\treturn {\n\t\tgetMovement: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\treturn fsm?.getMovement() ?? { moveX: 0, moveY: 0 };\n\t\t},\n\t\tgetCurrentStep: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\treturn fsm?.getCurrentStep() ?? null;\n\t\t},\n\t\tgetProgress: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\treturn fsm?.getProgress() ?? { stepIndex: 0, totalSteps: 0, stepTimeRemaining: 0, done: true };\n\t\t},\n\t\tpause: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\tfsm?.pause();\n\t\t},\n\t\tresume: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\tfsm?.resume();\n\t\t},\n\t\treset: () => {\n\t\t\tconst fsm = ref.fsm as MovementSequence2DFSM | undefined;\n\t\t\tfsm?.reset();\n\t\t},\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// System\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * MovementSequence2DSystem\n *\n * Stage-level system that:\n * - Finds entities with this behavior attached\n * - Updates FSM with delta time each frame\n * - Consumers read getMovement() to get current velocity\n */\nclass MovementSequence2DSystem implements BehaviorSystem {\n\tconstructor(private world: any) {}\n\n\tupdate(_ecs: IWorld, delta: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tconst gameEntity = entity as any;\n\n\t\t\tif (typeof gameEntity.getBehaviorRefs !== 'function') continue;\n\n\t\t\tconst refs = gameEntity.getBehaviorRefs();\n\t\t\tconst sequenceRef = refs.find(\n\t\t\t\t(r: any) => r.descriptor.key === Symbol.for('zylem:behavior:movement-sequence-2d')\n\t\t\t);\n\n\t\t\tif (!sequenceRef) continue;\n\n\t\t\tconst options = sequenceRef.options as MovementSequence2DOptions;\n\n\t\t\t// Create and init FSM lazily on first update\n\t\t\tif (!sequenceRef.fsm) {\n\t\t\t\tsequenceRef.fsm = new MovementSequence2DFSM();\n\t\t\t\tsequenceRef.fsm.init(options.sequence, options.loop);\n\t\t\t}\n\n\t\t\t// Update FSM - advances time and handles step transitions\n\t\t\tsequenceRef.fsm.update(delta);\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {}\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior Definition\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * MovementSequence2DBehavior\n *\n * @example\n * ```ts\n * import { MovementSequence2DBehavior } from \"@zylem/game-lib\";\n *\n * const enemy = makeMoveable(createSprite({ ... }));\n * const sequence = enemy.use(MovementSequence2DBehavior, {\n * sequence: [\n * { name: 'right', moveX: 3, moveY: 0, timeInSeconds: 2 },\n * { name: 'left', moveX: -3, moveY: 0, timeInSeconds: 2 },\n * ],\n * loop: true,\n * });\n *\n * enemy.onUpdate(({ me }) => {\n * const { moveX, moveY } = sequence.getMovement();\n * me.moveXY(moveX, moveY);\n * });\n * ```\n */\nexport const MovementSequence2DBehavior = defineBehavior({\n\tname: 'movement-sequence-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) => new MovementSequence2DSystem(ctx.world),\n\tcreateHandle: createMovementSequence2DHandle,\n});\n","import { GameEntity } from '../entities/entity';\nimport { Ricochet2DHandle, Ricochet2DResult } from '../behaviors/ricochet-2d/ricochet-2d.descriptor';\nimport { WorldBoundary2DHandle } from '../behaviors/world-boundary-2d/world-boundary-2d.descriptor';\nimport { MoveableEntity } from '../actions/capabilities/moveable';\n\n/**\n * Coordinator that bridges WorldBoundary2DBehavior and Ricochet2DBehavior.\n * \n * Automatically handles:\n * 1. Checking boundary hits\n * 2. Computing collision normals\n * 3. Requesting ricochet result\n * 4. Applying movement\n */\nexport class BoundaryRicochetCoordinator {\n constructor(\n private entity: GameEntity<any> & MoveableEntity,\n private boundary: WorldBoundary2DHandle,\n private ricochet: Ricochet2DHandle\n ) {}\n\n /**\n * Update loop - call this every frame\n */\n public update(): Ricochet2DResult | null {\n const hits = this.boundary.getLastHits();\n if (!hits) return null;\n\n const anyHit = hits.left || hits.right || hits.top || hits.bottom;\n if (!anyHit) return null;\n\n // Compute collision normal from boundary hits\n let normalX = 0;\n let normalY = 0;\n if (hits.left) normalX = 1;\n if (hits.right) normalX = -1;\n if (hits.bottom) normalY = 1;\n if (hits.top) normalY = -1;\n\n // Compute ricochet result\n return this.ricochet.getRicochet({\n entity: this.entity,\n contact: { normal: { x: normalX, y: normalY } },\n });\n }\n}\n"],"mappings":"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","names":["ThrusterState","ThrusterEvent","StateMachine","t","ScreenWrapState","ScreenWrapEvent","defaultOptions","StateMachine","t","WorldBoundary2DState","WorldBoundary2DEvent","defaultOptions","StateMachine","t","Ricochet2DState","Ricochet2DEvent","defaultOptions","StateMachine","t","MovementSequence2DState","MovementSequence2DEvent","step","defaultOptions"]}
@@ -0,0 +1,26 @@
1
+ import { Vector2 } from 'three';
2
+ import { G as GameEntity } from './world-C8tQ7Plj.js';
3
+ import { b as Stage } from './stage-types-Bd-KtcYT.js';
4
+ import * as _sinclair_typebox from '@sinclair/typebox';
5
+ import { Static } from '@sinclair/typebox';
6
+
7
+ interface EntitySpawner {
8
+ spawn: (stage: Stage, x: number, y: number) => Promise<GameEntity<any>>;
9
+ spawnRelative: (source: any, stage: Stage, offset?: Vector2) => Promise<any | void>;
10
+ }
11
+ declare function entitySpawner(factory: (x: number, y: number) => Promise<any> | GameEntity<any>): EntitySpawner;
12
+
13
+ declare const StageSchema: _sinclair_typebox.TObject<{
14
+ id: _sinclair_typebox.TString;
15
+ name: _sinclair_typebox.TOptional<_sinclair_typebox.TString>;
16
+ entities: _sinclair_typebox.TArray<_sinclair_typebox.TObject<{
17
+ id: _sinclair_typebox.TString;
18
+ type: _sinclair_typebox.TString;
19
+ position: _sinclair_typebox.TOptional<_sinclair_typebox.TTuple<[_sinclair_typebox.TNumber, _sinclair_typebox.TNumber]>>;
20
+ data: _sinclair_typebox.TOptional<_sinclair_typebox.TRecord<_sinclair_typebox.TString, _sinclair_typebox.TAny>>;
21
+ }>>;
22
+ assets: _sinclair_typebox.TOptional<_sinclair_typebox.TArray<_sinclair_typebox.TString>>;
23
+ }>;
24
+ type StageBlueprint = Static<typeof StageSchema>;
25
+
26
+ export { type StageBlueprint as S, entitySpawner as e };
@@ -1,7 +1,6 @@
1
- import { Group, Mesh, Object3D, Camera, Vector2, WebGLRenderer, Scene, Vector3 } from 'three';
2
- import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
1
+ import { Object3D, Camera, Vector2, WebGLRenderer, Scene, Vector3 } from 'three';
3
2
  import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
4
- import { RigidBody, Collider, KinematicCharacterController } from '@dimforge/rapier3d-compat';
3
+ import { S as StageEntity } from './entity-Bq_eNEDI.js';
5
4
 
6
5
  declare const Perspectives: {
7
6
  readonly FirstPerson: "first-person";
@@ -12,28 +11,13 @@ declare const Perspectives: {
12
11
  };
13
12
  type PerspectiveType = (typeof Perspectives)[keyof typeof Perspectives];
14
13
 
15
- type LifecycleFunction<T> = (params?: any) => void;
16
- interface Entity<T = any> {
17
- setup: (entity: T) => void;
18
- destroy: () => void;
19
- update: LifecycleFunction<T>;
20
- type: string;
21
- _collision?: (entity: any, other: any, globals?: any) => void;
22
- _destroy?: (globals?: any) => void;
23
- name?: string;
24
- tag?: Set<string>;
14
+ interface CameraDebugState {
15
+ enabled: boolean;
16
+ selected: string[];
25
17
  }
26
- interface StageEntity extends Entity {
27
- uuid: string;
28
- body: RigidBody;
29
- group: Group;
30
- mesh: Mesh;
31
- instanceId: number;
32
- collider: Collider;
33
- controlledRotation: boolean;
34
- characterController: KinematicCharacterController;
35
- name: string;
36
- markedForRemoval: boolean;
18
+ interface CameraDebugDelegate {
19
+ subscribe(listener: (state: CameraDebugState) => void): () => void;
20
+ resolveTarget(uuid: string): Object3D | null;
37
21
  }
38
22
 
39
23
  /**
@@ -49,31 +33,18 @@ interface PerspectiveController {
49
33
  update(delta: number): void;
50
34
  resize(width: number, height: number): void;
51
35
  }
52
- interface CameraDebugState {
53
- enabled: boolean;
54
- selected: string[];
55
- }
56
- interface CameraDebugDelegate {
57
- subscribe(listener: (state: CameraDebugState) => void): () => void;
58
- resolveTarget(uuid: string): Object3D | null;
59
- }
60
36
  declare class ZylemCamera {
61
- cameraRig: Object3D;
37
+ cameraRig: Object3D | null;
62
38
  camera: Camera;
63
39
  screenResolution: Vector2;
64
40
  renderer: WebGLRenderer;
65
41
  composer: EffectComposer;
66
42
  _perspective: PerspectiveType;
67
- orbitControls: OrbitControls | null;
68
43
  target: StageEntity | null;
69
44
  sceneRef: Scene | null;
70
45
  frustumSize: number;
71
46
  perspectiveController: PerspectiveController | null;
72
- debugDelegate: CameraDebugDelegate | null;
73
- private debugUnsubscribe;
74
- private debugStateSnapshot;
75
- private orbitTarget;
76
- private orbitTargetWorldPos;
47
+ private orbitController;
77
48
  constructor(perspective: PerspectiveType, screenResolution: Vector2, frustumSize?: number);
78
49
  /**
79
50
  * Setup the camera with a scene
@@ -83,6 +54,10 @@ declare class ZylemCamera {
83
54
  * Update camera and render
84
55
  */
85
56
  update(delta: number): void;
57
+ /**
58
+ * Check if debug mode is active (orbit controls taking over camera)
59
+ */
60
+ isDebugModeActive(): boolean;
86
61
  /**
87
62
  * Dispose renderer, composer, controls, and detach from scene
88
63
  */
@@ -115,15 +90,14 @@ declare class ZylemCamera {
115
90
  private moveCamera;
116
91
  move(position: Vector3): void;
117
92
  rotate(pitch: number, yaw: number, roll: number): void;
93
+ /**
94
+ * Check if this perspective type needs a camera rig
95
+ */
96
+ private needsRig;
118
97
  /**
119
98
  * Get the DOM element for the renderer
120
99
  */
121
100
  getDomElement(): HTMLCanvasElement;
122
- private applyDebugState;
123
- private enableOrbitControls;
124
- private disableOrbitControls;
125
- private updateOrbitTargetFromSelection;
126
- private detachDebugDelegate;
127
101
  }
128
102
 
129
103
  interface CameraOptions {
@@ -137,6 +111,6 @@ declare class CameraWrapper {
137
111
  cameraRef: ZylemCamera;
138
112
  constructor(camera: ZylemCamera);
139
113
  }
140
- declare function camera(options: CameraOptions): CameraWrapper;
114
+ declare function createCamera(options: CameraOptions): CameraWrapper;
141
115
 
142
- export { type CameraDebugDelegate as C, type Entity as E, type LifecycleFunction as L, type PerspectiveType as P, ZylemCamera as Z, Perspectives as a, type CameraDebugState as b, camera as c, CameraWrapper as d };
116
+ export { type CameraDebugDelegate as C, type PerspectiveType as P, ZylemCamera as Z, type CameraDebugState as a, CameraWrapper as b, createCamera as c, Perspectives as d };
package/dist/camera.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { P as PerspectiveType, a as Perspectives, c as camera } from './camera-Dk-fOVZE.js';
1
+ export { P as PerspectiveType, d as Perspectives, c as createCamera } from './camera-CeJPAgGg.js';
2
2
  import 'three';
3
- import 'three/addons/controls/OrbitControls.js';
4
3
  import 'three/examples/jsm/postprocessing/EffectComposer.js';
4
+ import './entity-Bq_eNEDI.js';
5
5
  import '@dimforge/rapier3d-compat';