@zylem/game-lib 0.6.0 → 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\";\n\n/**\n * Options for configuring entity collision behavior.\n */\nexport interface CollisionOptions {\n\tstatic?: boolean;\n\tsensor?: boolean;\n\tsize?: Vector3;\n\tposition?: Vector3;\n\tcollisionType?: string;\n\tcollisionFilter?: string[];\n}\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 { getOrCreateCollisionGroupId } from \"./collision-builder\";\n\n/**\n * A branded bitmask representing a set of collision types.\n */\nexport type CollisionMask = number & { readonly __brand: \"CollisionMask\" };\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","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"]}
@@ -1,6 +1,6 @@
1
1
  import { Vector2 } from 'three';
2
- import { G as GameEntity } from './entity-COvRtFNG.js';
3
- import { b as Stage } from './stage-types-CD21XoIU.js';
2
+ import { G as GameEntity } from './world-C8tQ7Plj.js';
3
+ import { b as Stage } from './stage-types-Bd-KtcYT.js';
4
4
  import * as _sinclair_typebox from '@sinclair/typebox';
5
5
  import { Static } from '@sinclair/typebox';
6
6
 
@@ -111,6 +111,6 @@ declare class CameraWrapper {
111
111
  cameraRef: ZylemCamera;
112
112
  constructor(camera: ZylemCamera);
113
113
  }
114
- declare function camera(options: CameraOptions): CameraWrapper;
114
+ declare function createCamera(options: CameraOptions): CameraWrapper;
115
115
 
116
- export { type CameraDebugDelegate as C, 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,4 +1,4 @@
1
- export { P as PerspectiveType, a as Perspectives, c as camera } from './camera-CpbDr4-V.js';
1
+ export { P as PerspectiveType, d as Perspectives, c as createCamera } from './camera-CeJPAgGg.js';
2
2
  import 'three';
3
3
  import 'three/examples/jsm/postprocessing/EffectComposer.js';
4
4
  import './entity-Bq_eNEDI.js';
package/dist/camera.js CHANGED
@@ -28,11 +28,11 @@ var ThirdPersonCamera = class {
28
28
  * Setup the third person camera controller
29
29
  */
30
30
  setup(params) {
31
- const { screenResolution, renderer, scene, camera: camera2 } = params;
31
+ const { screenResolution, renderer, scene, camera } = params;
32
32
  this.screenResolution = screenResolution;
33
33
  this.renderer = renderer;
34
34
  this.scene = scene;
35
- this.cameraRef = camera2;
35
+ this.cameraRef = camera;
36
36
  }
37
37
  /**
38
38
  * Update the third person camera
@@ -73,11 +73,11 @@ var Fixed2DCamera = class {
73
73
  * Setup the fixed 2D camera controller
74
74
  */
75
75
  setup(params) {
76
- const { screenResolution, renderer, scene, camera: camera2 } = params;
76
+ const { screenResolution, renderer, scene, camera } = params;
77
77
  this.screenResolution = screenResolution;
78
78
  this.renderer = renderer;
79
79
  this.scene = scene;
80
- this.cameraRef = camera2;
80
+ this.cameraRef = camera;
81
81
  this.cameraRef.camera.position.set(0, 0, 10);
82
82
  this.cameraRef.camera.lookAt(0, 0, 0);
83
83
  }
@@ -103,11 +103,33 @@ import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer
103
103
  // src/lib/graphics/render-pass.ts
104
104
  import * as THREE from "three";
105
105
 
106
- // src/lib/graphics/shaders/fragment/standard.glsl
107
- var standard_default = "uniform sampler2D tDiffuse;\nvarying vec2 vUv;\n\nvoid main() {\n vec4 texel = texture2D( tDiffuse, vUv );\n\n gl_FragColor = texel;\n}";
106
+ // src/lib/graphics/shaders/vertex/object.shader.ts
107
+ var objectVertexShader = `
108
+ uniform vec2 uvScale;
109
+ varying vec2 vUv;
108
110
 
109
- // src/lib/graphics/shaders/vertex/standard.glsl
110
- var standard_default2 = "varying vec2 vUv;\n\nvoid main() {\n vUv = uv;\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
111
+ void main() {
112
+ vUv = uv;
113
+ vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
114
+ gl_Position = projectionMatrix * mvPosition;
115
+ }
116
+ `;
117
+
118
+ // src/lib/graphics/shaders/standard.shader.ts
119
+ var fragment = `
120
+ uniform sampler2D tDiffuse;
121
+ varying vec2 vUv;
122
+
123
+ void main() {
124
+ vec4 texel = texture2D( tDiffuse, vUv );
125
+
126
+ gl_FragColor = texel;
127
+ }
128
+ `;
129
+ var standardShader = {
130
+ vertex: objectVertexShader,
131
+ fragment
132
+ };
111
133
 
112
134
  // src/lib/graphics/render-pass.ts
113
135
  import { WebGLRenderTarget } from "three";
@@ -120,12 +142,12 @@ var RenderPass = class extends Pass {
120
142
  rgbRenderTarget;
121
143
  normalRenderTarget;
122
144
  normalMaterial;
123
- constructor(resolution, scene, camera2) {
145
+ constructor(resolution, scene, camera) {
124
146
  super();
125
147
  this.resolution = resolution;
126
148
  this.fsQuad = new FullScreenQuad(this.material());
127
149
  this.scene = scene;
128
- this.camera = camera2;
150
+ this.camera = camera;
129
151
  this.rgbRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
130
152
  this.normalRenderTarget = new WebGLRenderTarget(resolution.x * 4, resolution.y * 4);
131
153
  this.normalMaterial = new THREE.MeshNormalMaterial();
@@ -166,8 +188,8 @@ var RenderPass = class extends Pass {
166
188
  )
167
189
  }
168
190
  },
169
- vertexShader: standard_default2,
170
- fragmentShader: standard_default
191
+ vertexShader: standardShader.vertex,
192
+ fragmentShader: standardShader.fragment
171
193
  });
172
194
  }
173
195
  dispose() {
@@ -193,6 +215,8 @@ import { OrbitControls } from "three/addons/controls/OrbitControls.js";
193
215
  var CameraOrbitController = class {
194
216
  camera;
195
217
  domElement;
218
+ cameraRig = null;
219
+ sceneRef = null;
196
220
  orbitControls = null;
197
221
  orbitTarget = null;
198
222
  orbitTargetWorldPos = new Vector32();
@@ -203,9 +227,22 @@ var CameraOrbitController = class {
203
227
  savedCameraPosition = null;
204
228
  savedCameraQuaternion = null;
205
229
  savedCameraZoom = null;
206
- constructor(camera2, domElement) {
207
- this.camera = camera2;
230
+ savedCameraLocalPosition = null;
231
+ // Saved debug camera state for restoration when re-entering debug mode
232
+ savedDebugCameraPosition = null;
233
+ savedDebugCameraQuaternion = null;
234
+ savedDebugCameraZoom = null;
235
+ savedDebugOrbitTarget = null;
236
+ constructor(camera, domElement, cameraRig) {
237
+ this.camera = camera;
208
238
  this.domElement = domElement;
239
+ this.cameraRig = cameraRig ?? null;
240
+ }
241
+ /**
242
+ * Set the scene reference for adding/removing camera when detaching from rig.
243
+ */
244
+ setScene(scene) {
245
+ this.sceneRef = scene;
209
246
  }
210
247
  /**
211
248
  * Check if debug mode is currently active (orbit controls enabled).
@@ -265,11 +302,15 @@ var CameraOrbitController = class {
265
302
  };
266
303
  if (state.enabled && !wasEnabled) {
267
304
  this.saveCameraState();
305
+ this.detachCameraFromRig();
268
306
  this.enableOrbitControls();
307
+ this.restoreDebugCameraState();
269
308
  this.updateOrbitTargetFromSelection(state.selected);
270
309
  } else if (!state.enabled && wasEnabled) {
310
+ this.saveDebugCameraState();
271
311
  this.orbitTarget = null;
272
312
  this.disableOrbitControls();
313
+ this.reattachCameraToRig();
273
314
  this.restoreCameraState();
274
315
  } else if (state.enabled) {
275
316
  this.updateOrbitTargetFromSelection(state.selected);
@@ -355,6 +396,71 @@ var CameraOrbitController = class {
355
396
  this.savedCameraZoom = null;
356
397
  }
357
398
  }
399
+ /**
400
+ * Save debug camera state when exiting debug mode.
401
+ */
402
+ saveDebugCameraState() {
403
+ this.savedDebugCameraPosition = this.camera.position.clone();
404
+ this.savedDebugCameraQuaternion = this.camera.quaternion.clone();
405
+ if ("zoom" in this.camera) {
406
+ this.savedDebugCameraZoom = this.camera.zoom;
407
+ }
408
+ if (this.orbitControls) {
409
+ this.savedDebugOrbitTarget = this.orbitControls.target.clone();
410
+ }
411
+ }
412
+ /**
413
+ * Restore debug camera state when re-entering debug mode.
414
+ */
415
+ restoreDebugCameraState() {
416
+ if (this.savedDebugCameraPosition) {
417
+ this.camera.position.copy(this.savedDebugCameraPosition);
418
+ }
419
+ if (this.savedDebugCameraQuaternion) {
420
+ this.camera.quaternion.copy(this.savedDebugCameraQuaternion);
421
+ }
422
+ if (this.savedDebugCameraZoom !== null && "zoom" in this.camera) {
423
+ this.camera.zoom = this.savedDebugCameraZoom;
424
+ this.camera.updateProjectionMatrix?.();
425
+ }
426
+ if (this.savedDebugOrbitTarget && this.orbitControls) {
427
+ this.orbitControls.target.copy(this.savedDebugOrbitTarget);
428
+ }
429
+ }
430
+ /**
431
+ * Detach camera from its rig to allow free orbit movement in debug mode.
432
+ * Preserves the camera's world position.
433
+ */
434
+ detachCameraFromRig() {
435
+ if (!this.cameraRig || this.camera.parent !== this.cameraRig) {
436
+ return;
437
+ }
438
+ this.savedCameraLocalPosition = this.camera.position.clone();
439
+ const worldPos = new Vector32();
440
+ this.camera.getWorldPosition(worldPos);
441
+ this.cameraRig.remove(this.camera);
442
+ if (this.sceneRef) {
443
+ this.sceneRef.add(this.camera);
444
+ }
445
+ this.camera.position.copy(worldPos);
446
+ }
447
+ /**
448
+ * Reattach camera to its rig when exiting debug mode.
449
+ * Restores the camera's local position relative to the rig.
450
+ */
451
+ reattachCameraToRig() {
452
+ if (!this.cameraRig || this.camera.parent === this.cameraRig) {
453
+ return;
454
+ }
455
+ if (this.sceneRef && this.camera.parent === this.sceneRef) {
456
+ this.sceneRef.remove(this.camera);
457
+ }
458
+ this.cameraRig.add(this.camera);
459
+ if (this.savedCameraLocalPosition) {
460
+ this.camera.position.copy(this.savedCameraLocalPosition);
461
+ this.savedCameraLocalPosition = null;
462
+ }
463
+ }
358
464
  };
359
465
 
360
466
  // src/lib/camera/zylem-camera.ts
@@ -392,7 +498,7 @@ var ZylemCamera = class {
392
498
  this.camera.lookAt(new Vector33(0, 0, 0));
393
499
  }
394
500
  this.initializePerspectiveController();
395
- this.orbitController = new CameraOrbitController(this.camera, this.renderer.domElement);
501
+ this.orbitController = new CameraOrbitController(this.camera, this.renderer.domElement, this.cameraRig);
396
502
  }
397
503
  /**
398
504
  * Setup the camera with a scene
@@ -412,6 +518,7 @@ var ZylemCamera = class {
412
518
  camera: this
413
519
  });
414
520
  }
521
+ this.orbitController?.setScene(scene);
415
522
  this.renderer.setAnimationLoop((delta) => {
416
523
  this.update(delta || 0);
417
524
  });
@@ -588,11 +695,11 @@ var ZylemCamera = class {
588
695
  // src/lib/camera/camera.ts
589
696
  var CameraWrapper = class {
590
697
  cameraRef;
591
- constructor(camera2) {
592
- this.cameraRef = camera2;
698
+ constructor(camera) {
699
+ this.cameraRef = camera;
593
700
  }
594
701
  };
595
- function camera(options) {
702
+ function createCamera(options) {
596
703
  const screenResolution = options.screenResolution || new Vector23(window.innerWidth, window.innerHeight);
597
704
  let frustumSize = 10;
598
705
  if (options.perspective === "fixed-2d") {
@@ -605,6 +712,6 @@ function camera(options) {
605
712
  }
606
713
  export {
607
714
  Perspectives,
608
- camera
715
+ createCamera
609
716
  };
610
717
  //# sourceMappingURL=camera.js.map