ecspresso 0.16.2 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,13 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/plugins/physics/collision.ts", "../src/utils/layer-bit-registry.ts", "../src/utils/narrowphase.ts", "../src/plugins/physics/collision3D.ts", "../src/utils/narrowphase3D.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Collision Plugin for ECSpresso\n *\n * Provides layer-based collision detection with events.\n * Uses worldTransform for position (world-space collision).\n * Supports AABB and circle colliders.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { TransformWorldConfig } from '../spatial/transform';\nimport { fillBaseColliderInfo, detectCollisions, createBroadphaseScratch, AABB_SHAPE, type Contact, type BaseColliderInfo } from '../../utils/narrowphase';\nimport type { SpatialIndex } from '../../utils/spatial-hash';\n\n// ==================== Component Types ====================\n\n/**\n * Axis-Aligned Bounding Box collider.\n */\nexport interface AABBCollider {\n\t/** Width of the bounding box */\n\twidth: number;\n\t/** Height of the bounding box */\n\theight: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Circle collider.\n */\nexport interface CircleCollider {\n\t/** Radius of the circle */\n\tradius: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Collision layer configuration.\n */\nexport interface CollisionLayer<L extends string = never> {\n\t/** The layer this entity belongs to */\n\tlayer: L;\n\t/** Layers this entity can collide with */\n\tcollidesWith: readonly L[];\n}\n\n/**\n * Component types provided by the collision plugin.\n * Included automatically via `.withPlugin(createCollisionPlugin())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createCollisionPlugin())\n * .withComponentTypes<{ sprite: Sprite; enemy: boolean }>()\n * .build();\n * ```\n */\nexport interface CollisionComponentTypes<L extends string = never> {\n\taabbCollider: AABBCollider;\n\tcircleCollider: CircleCollider;\n\tcollisionLayer: CollisionLayer<L>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two entities collide.\n *\n * Normal components are flattened (`normalX`/`normalY`) rather than nested\n * in a sub-object to avoid a per-event allocation in the collision hot path.\n */\nexport interface CollisionEvent<L extends string = never> {\n\t/** First entity in the collision */\n\tentityA: number;\n\t/** Second entity in the collision */\n\tentityB: number;\n\t/** Layer of the first entity */\n\tlayerA: L;\n\t/** Layer of the second entity */\n\tlayerB: L;\n\t/** Contact normal X, pointing from entityA toward entityB */\n\tnormalX: number;\n\t/** Contact normal Y, pointing from entityA toward entityB */\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Event types provided by the collision plugin.\n */\nexport interface CollisionEventTypes<L extends string = never> {\n\tcollision: CollisionEvent<L>;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the collision plugin.\n */\nexport interface CollisionPluginOptions<G extends string = 'physics'> extends BasePluginOptions<G> {\n\t/** Name of the collision event (default: 'collision') */\n\tcollisionEventName?: string;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an AABB collider component.\n *\n * @param width Width of the bounding box\n * @param height Height of the bounding box\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * });\n * ```\n */\nexport function createAABBCollider(\n\twidth: number,\n\theight: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { aabbCollider: AABBCollider } {\n\tconst collider: AABBCollider = { width, height };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { aabbCollider: collider };\n}\n\n/**\n * Create a circle collider component.\n *\n * @param radius Radius of the circle\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createCircleCollider(25),\n * });\n * ```\n */\nexport function createCircleCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { circleCollider: CircleCollider } {\n\tconst collider: CircleCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { circleCollider: collider };\n}\n\n/**\n * Create a collision layer component.\n *\n * @param layer The layer this entity belongs to\n * @param collidesWith Layers this entity can collide with\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...createCollisionLayer('player', ['enemy', 'obstacle']),\n * });\n * ```\n */\nexport function createCollisionLayer<L extends string>(\n\tlayer: L,\n\tcollidesWith: readonly L[]\n): Pick<CollisionComponentTypes<L>, 'collisionLayer'> {\n\treturn {\n\t\tcollisionLayer: { layer, collidesWith },\n\t};\n}\n\n/**\n * Layer factory result from defineCollisionLayers.\n */\nexport type LayerFactories<T extends Record<string, readonly string[]>> = {\n\t[K in keyof T]: () => Pick<CollisionComponentTypes<Extract<keyof T, string>>, 'collisionLayer'>;\n};\n\n/**\n * Extract layer names from a `defineCollisionLayers` result for use with\n * `createCollisionPairHandler`'s `L` type parameter.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n * ```\n */\nexport type LayersOf<T> = Extract<keyof T, string>;\n\n/**\n * Define collision layer relationships and get factory functions.\n *\n * @param rules Object mapping layer names to arrays of layers they collide with\n * @returns Object with factory functions for each layer\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({\n * player: ['enemy', 'enemyProjectile'],\n * playerProjectile: ['enemy'],\n * enemy: ['playerProjectile'],\n * enemyProjectile: ['player'],\n * });\n *\n * // Usage\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\n/**\n * Validates that all `collidesWith` values reference actual layer keys.\n * Catches typos at compile time.\n */\ntype ValidateCollidesWith<T> = {\n\t[K in keyof T]: T[K] extends readonly (infer V)[]\n\t\t? [V] extends [Extract<keyof T, string>] ? T[K] : readonly Extract<keyof T, string>[]\n\t\t: never;\n};\n\nexport function defineCollisionLayers<const T extends Record<string, readonly string[]>>(\n\trules: T & ValidateCollidesWith<T>\n): LayerFactories<T> {\n\ttype L = Extract<keyof T, string>;\n\tconst factories = {} as LayerFactories<T>;\n\n\tfor (const layer of Object.keys(rules) as Array<L>) {\n\t\tconst collidesWith = rules[layer] as readonly L[];\n\t\tfactories[layer] = () => createCollisionLayer<L>(layer, collidesWith);\n\t}\n\n\treturn factories;\n}\n\n// ==================== Collision Pair Handler ====================\n\n/**\n * Callback for a collision pair handler.\n *\n * @param firstEntityId Entity belonging to the first layer in the pair key\n * @param secondEntityId Entity belonging to the second layer in the pair key\n * @param ecs The ECS world instance (passed through from the subscriber)\n */\nexport type CollisionPairCallback<W = unknown> = (\n\tfirstEntityId: number,\n\tsecondEntityId: number,\n\tecs: W,\n) => void;\n\ninterface PairEntry<W> {\n\tcallback: CollisionPairCallback<W>;\n\tswapped: boolean;\n}\n\nfunction parsePairKey(key: string): [string, string] {\n\tconst colonIndex = key.indexOf(':');\n\tif (colonIndex === -1) {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": must contain a colon separator (e.g. \"player:enemy\")`);\n\t}\n\tconst layerA = key.slice(0, colonIndex);\n\tconst layerB = key.slice(colonIndex + 1);\n\tif (layerA === '' || layerB === '') {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": layer names must not be empty`);\n\t}\n\treturn [layerA, layerB];\n}\n\n/**\n * Create a collision pair handler that routes collision events to\n * layer-pair-specific callbacks.\n *\n * Registering `\"a:b\"` automatically handles both `(layerA=a, layerB=b)` and\n * `(layerA=b, layerB=a)`. Entity arguments are swapped to match the declared\n * key order. If both `\"a:b\"` and `\"b:a\"` are explicitly registered, each gets\n * its own handler with no implicit reverse.\n *\n * @typeParam W - The ECS world type (e.g. `ECSpresso<C, E, R>`). Defaults to `unknown`.\n * @typeParam L - Union of valid layer names. Defaults to `string`.\n * Provide specific layer names for compile-time key validation:\n * `createCollisionPairHandler<ECS, keyof typeof layers>({...})`\n *\n * @param pairs Object mapping `\"layerA:layerB\"` keys to callbacks\n * @returns A dispatch function to call with collision event data and ECS instance\n *\n * @example\n * ```typescript\n * // Basic usage:\n * const handler = createCollisionPairHandler<ECS>({\n * 'playerProjectile:enemy': (projectileId, enemyId, ecs) => {\n * ecs.commands.removeEntity(projectileId);\n * },\n * });\n *\n * // With layer name validation:\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n *\n * ecs.eventBus.subscribe('collision', (data) => handler({ data, ecs }));\n * ```\n */\nexport function createCollisionPairHandler<W = unknown, L extends string = string>(\n\tpairs: { [K in `${L}:${L}`]?: CollisionPairCallback<W> }\n): (ctx: { data: CollisionEvent<L>; ecs: W }) => void;\nexport function createCollisionPairHandler<W = unknown>(\n\tpairs: Record<string, CollisionPairCallback<W> | undefined>\n): (ctx: { data: CollisionEvent<string>; ecs: W }) => void {\n\tconst lookup = new Map<string, PairEntry<W>>();\n\tconst explicitKeys = new Set<string>();\n\n\t// First pass: collect all explicit keys\n\tfor (const key of Object.keys(pairs)) {\n\t\tparsePairKey(key); // validate\n\t\texplicitKeys.add(key);\n\t}\n\n\t// Second pass: build lookup with forward + conditional reverse entries\n\tfor (const key of Object.keys(pairs)) {\n\t\tconst [layerA, layerB] = parsePairKey(key);\n\t\tconst callback = pairs[key];\n\t\tif (!callback) continue;\n\n\t\t// Forward entry\n\t\tlookup.set(key, { callback, swapped: false });\n\n\t\t// Reverse entry (only if the reverse key wasn't explicitly registered\n\t\t// and it's not a self-collision where forward === reverse)\n\t\tconst reverseKey = `${layerB}:${layerA}`;\n\t\tif (reverseKey !== key && !explicitKeys.has(reverseKey)) {\n\t\t\tlookup.set(reverseKey, { callback, swapped: true });\n\t\t}\n\t}\n\n\treturn function collisionPairDispatch({ data: event, ecs }: { data: CollisionEvent<string>; ecs: W }): void {\n\t\tconst entry = lookup.get(event.layerA + ':' + event.layerB);\n\t\tif (!entry) return;\n\n\t\tif (entry.swapped) {\n\t\t\tentry.callback(event.entityB, event.entityA, ecs);\n\t\t} else {\n\t\t\tentry.callback(event.entityA, event.entityB, ecs);\n\t\t}\n\t};\n}\n\n// ==================== Dependency Types ====================\n\n// ==================== Module-level Collision Callback ====================\n\ninterface CollisionEventBus<L extends string> {\n\tpublish(event: 'collision', data: CollisionEvent<L>): void;\n}\n\n/**\n * Module-level reusable collision event. Subscribers must consume\n * synchronously — same contract as the shared narrowphase Contact.\n */\nconst _collisionEvent: CollisionEvent<string> = {\n\tentityA: 0, entityB: 0, layerA: '', layerB: '',\n\tnormalX: 0, normalY: 0, depth: 0,\n};\n\nfunction onCollisionDetected<L extends string>(\n\ta: BaseColliderInfo<L>,\n\tb: BaseColliderInfo<L>,\n\tcontact: Contact,\n\teventBus: CollisionEventBus<L>,\n): void {\n\t_collisionEvent.entityA = a.entityId;\n\t_collisionEvent.entityB = b.entityId;\n\t_collisionEvent.layerA = a.layer;\n\t_collisionEvent.layerB = b.layer;\n\t_collisionEvent.normalX = contact.normalX;\n\t_collisionEvent.normalY = contact.normalY;\n\t_collisionEvent.depth = contact.depth;\n\teventBus.publish('collision', _collisionEvent as CollisionEvent<L>);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a collision plugin for ECSpresso.\n *\n * This plugin provides:\n * - Collision detection between entities with colliders\n * - AABB-AABB, circle-circle, and AABB-circle collision\n * - Layer-based filtering for collision pairs\n * - Deduplication of A-B / B-A collisions\n * - Automatic broadphase acceleration when spatialIndex resource is present\n *\n * Uses worldTransform for position (world-space collision detection).\n * The `layers` parameter is required for type inference — at runtime the\n * plugin does not consume it.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * const ecs = ECSpresso\n * .create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createCollisionPlugin({ layers }))\n * .build();\n *\n * // Entity with collision\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\nexport function createCollisionPlugin<L extends string, G extends string = 'physics'>(\n\toptions: CollisionPluginOptions<G> & { layers: LayerFactories<Record<L, readonly string[]>> }\n) {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options;\n\n\treturn definePlugin('collision')\n\t\t.withComponentTypes<CollisionComponentTypes<L>>()\n\t\t.withEventTypes<CollisionEventTypes<L>>()\n\t\t.withLabels<'collision-detection'>()\n\t\t.withGroups<G>()\n\t\t.requires<TransformWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// Grow-only pool of BaseColliderInfo slots reused across frames.\n\t\t\t// Steady-state: zero allocations per frame once the pool is warm.\n\t\t\tconst colliderPool: BaseColliderInfo<L>[] = [];\n\t\t\tconst broadphaseScratch = createBroadphaseScratch<BaseColliderInfo<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tworld\n\t\t\t\t.addSystem('collision-detection')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('collidables', {\n\t\t\t\t\twith: ['worldTransform', 'collisionLayer'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\t// TODO(perf): collider shape is discovered via two ecs.getComponent\n\t\t\t\t\t// calls per entity per frame because the query can't express\n\t\t\t\t\t// \"aabbCollider OR circleCollider\". Splitting into two queries\n\t\t\t\t\t// (aabb-bearing, circle-bearing) would eliminate these lookups at\n\t\t\t\t\t// the cost of two pool-fill passes. Keep in sync with collision3D.\n\t\t\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\t\t\tconst { worldTransform, collisionLayer } = entity.components;\n\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabbCollider');\n\t\t\t\t\t\tconst circle = aabb ? undefined : ecs.getComponent(entity.id, 'circleCollider');\n\t\t\t\t\t\tif (!aabb && !circle) continue;\n\n\t\t\t\t\t\tlet slot = colliderPool[count];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\tslot = {\n\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t\tx: worldTransform.x,\n\t\t\t\t\t\t\t\ty: worldTransform.y,\n\t\t\t\t\t\t\t\tlayer: collisionLayer.layer,\n\t\t\t\t\t\t\t\tcollidesWith: collisionLayer.collidesWith,\n\t\t\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\t\t\tshape: AABB_SHAPE,\n\t\t\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcolliderPool[count] = slot;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform.x, worldTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb, circle,\n\t\t\t\t\t\t)) continue;\n\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex>('spatialIndex');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions(colliderPool, count, broadphaseScratch, cachedSI, onCollisionDetected<L>, ecs.eventBus);\n\t\t\t\t});\n\t\t});\n}\n\n",
5
+ "/**\n * Collision Plugin for ECSpresso\n *\n * Provides layer-based collision detection with events.\n * Uses worldTransform for position (world-space collision).\n * Supports AABB and circle colliders.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { TransformWorldConfig } from '../spatial/transform';\nimport { fillBaseColliderInfo, detectCollisions, createBroadphaseScratch, AABB_SHAPE, type Contact, type BaseColliderInfo } from '../../utils/narrowphase';\nimport type { SpatialIndex } from '../../utils/spatial-hash';\n\n// ==================== Component Types ====================\n\n/**\n * Axis-Aligned Bounding Box collider.\n */\nexport interface AABBCollider {\n\t/** Width of the bounding box */\n\twidth: number;\n\t/** Height of the bounding box */\n\theight: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Circle collider.\n */\nexport interface CircleCollider {\n\t/** Radius of the circle */\n\tradius: number;\n\t/** X offset from entity position (default: 0) */\n\toffsetX?: number;\n\t/** Y offset from entity position (default: 0) */\n\toffsetY?: number;\n}\n\n/**\n * Collision layer configuration.\n */\nexport interface CollisionLayer<L extends string = never> {\n\t/** The layer this entity belongs to */\n\tlayer: L;\n\t/** Layers this entity can collide with */\n\tcollidesWith: readonly L[];\n}\n\n/**\n * Component types provided by the collision plugin.\n * Included automatically via `.withPlugin(createCollisionPlugin())`.\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createCollisionPlugin())\n * .withComponentTypes<{ sprite: Sprite; enemy: boolean }>()\n * .build();\n * ```\n */\nexport interface CollisionComponentTypes<L extends string = never> {\n\taabbCollider: AABBCollider;\n\tcircleCollider: CircleCollider;\n\tcollisionLayer: CollisionLayer<L>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two entities collide.\n *\n * Normal components are flattened (`normalX`/`normalY`) rather than nested\n * in a sub-object to avoid a per-event allocation in the collision hot path.\n */\nexport interface CollisionEvent<L extends string = never> {\n\t/** First entity in the collision */\n\tentityA: number;\n\t/** Second entity in the collision */\n\tentityB: number;\n\t/** Layer of the first entity */\n\tlayerA: L;\n\t/** Layer of the second entity */\n\tlayerB: L;\n\t/** Contact normal X, pointing from entityA toward entityB */\n\tnormalX: number;\n\t/** Contact normal Y, pointing from entityA toward entityB */\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Event types provided by the collision plugin.\n */\nexport interface CollisionEventTypes<L extends string = never> {\n\tcollision: CollisionEvent<L>;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the collision plugin.\n */\nexport interface CollisionPluginOptions<G extends string = 'physics'> extends BasePluginOptions<G> {\n\t/** Name of the collision event (default: 'collision') */\n\tcollisionEventName?: string;\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create an AABB collider component.\n *\n * @param width Width of the bounding box\n * @param height Height of the bounding box\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * });\n * ```\n */\nexport function createAABBCollider(\n\twidth: number,\n\theight: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { aabbCollider: AABBCollider } {\n\tconst collider: AABBCollider = { width, height };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { aabbCollider: collider };\n}\n\n/**\n * Create a circle collider component.\n *\n * @param radius Radius of the circle\n * @param offsetX X offset from entity position\n * @param offsetY Y offset from entity position\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createCircleCollider(25),\n * });\n * ```\n */\nexport function createCircleCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number\n): { circleCollider: CircleCollider } {\n\tconst collider: CircleCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\treturn { circleCollider: collider };\n}\n\n/**\n * Create a collision layer component.\n *\n * @param layer The layer this entity belongs to\n * @param collidesWith Layers this entity can collide with\n * @returns Component object suitable for spreading into spawn()\n *\n * @example\n * ```typescript\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...createCollisionLayer('player', ['enemy', 'obstacle']),\n * });\n * ```\n */\nexport function createCollisionLayer<L extends string>(\n\tlayer: L,\n\tcollidesWith: readonly L[]\n): Pick<CollisionComponentTypes<L>, 'collisionLayer'> {\n\treturn {\n\t\tcollisionLayer: { layer, collidesWith },\n\t};\n}\n\n/**\n * Layer factory result from defineCollisionLayers.\n */\nexport type LayerFactories<T extends Record<string, readonly string[]>> = {\n\t[K in keyof T]: () => Pick<CollisionComponentTypes<Extract<keyof T, string>>, 'collisionLayer'>;\n};\n\n/**\n * Extract layer names from a `defineCollisionLayers` result for use with\n * `createCollisionPairHandler`'s `L` type parameter.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n * ```\n */\nexport type LayersOf<T> = Extract<keyof T, string>;\n\n/**\n * Define collision layer relationships and get factory functions.\n *\n * @param rules Object mapping layer names to arrays of layers they collide with\n * @returns Object with factory functions for each layer\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({\n * player: ['enemy', 'enemyProjectile'],\n * playerProjectile: ['enemy'],\n * enemy: ['playerProjectile'],\n * enemyProjectile: ['player'],\n * });\n *\n * // Usage\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\n/**\n * Validates that all `collidesWith` values reference actual layer keys.\n * Catches typos at compile time.\n */\ntype ValidateCollidesWith<T> = {\n\t[K in keyof T]: T[K] extends readonly (infer V)[]\n\t\t? [V] extends [Extract<keyof T, string>] ? T[K] : readonly Extract<keyof T, string>[]\n\t\t: never;\n};\n\nexport function defineCollisionLayers<const T extends Record<string, readonly string[]>>(\n\trules: T & ValidateCollidesWith<T>\n): LayerFactories<T> {\n\ttype L = Extract<keyof T, string>;\n\tconst factories = {} as LayerFactories<T>;\n\n\tfor (const layer of Object.keys(rules) as Array<L>) {\n\t\tconst collidesWith = rules[layer] as readonly L[];\n\t\tfactories[layer] = () => createCollisionLayer<L>(layer, collidesWith);\n\t}\n\n\treturn factories;\n}\n\n// ==================== Collision Pair Handler ====================\n\n/**\n * Callback for a collision pair handler.\n *\n * @param firstEntityId Entity belonging to the first layer in the pair key\n * @param secondEntityId Entity belonging to the second layer in the pair key\n * @param ecs The ECS world instance (passed through from the subscriber)\n */\nexport type CollisionPairCallback<W = unknown> = (\n\tfirstEntityId: number,\n\tsecondEntityId: number,\n\tecs: W,\n) => void;\n\ninterface PairEntry<W> {\n\tcallback: CollisionPairCallback<W>;\n\tswapped: boolean;\n}\n\nfunction parsePairKey(key: string): [string, string] {\n\tconst colonIndex = key.indexOf(':');\n\tif (colonIndex === -1) {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": must contain a colon separator (e.g. \"player:enemy\")`);\n\t}\n\tconst layerA = key.slice(0, colonIndex);\n\tconst layerB = key.slice(colonIndex + 1);\n\tif (layerA === '' || layerB === '') {\n\t\tthrow new Error(`Invalid collision pair key \"${key}\": layer names must not be empty`);\n\t}\n\treturn [layerA, layerB];\n}\n\n/**\n * Create a collision pair handler that routes collision events to\n * layer-pair-specific callbacks.\n *\n * Registering `\"a:b\"` automatically handles both `(layerA=a, layerB=b)` and\n * `(layerA=b, layerB=a)`. Entity arguments are swapped to match the declared\n * key order. If both `\"a:b\"` and `\"b:a\"` are explicitly registered, each gets\n * its own handler with no implicit reverse.\n *\n * @typeParam W - The ECS world type (e.g. `ECSpresso<C, E, R>`). Defaults to `unknown`.\n * @typeParam L - Union of valid layer names. Defaults to `string`.\n * Provide specific layer names for compile-time key validation:\n * `createCollisionPairHandler<ECS, keyof typeof layers>({...})`\n *\n * @param pairs Object mapping `\"layerA:layerB\"` keys to callbacks\n * @returns A dispatch function to call with collision event data and ECS instance\n *\n * @example\n * ```typescript\n * // Basic usage:\n * const handler = createCollisionPairHandler<ECS>({\n * 'playerProjectile:enemy': (projectileId, enemyId, ecs) => {\n * ecs.commands.removeEntity(projectileId);\n * },\n * });\n *\n * // With layer name validation:\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * type Layer = LayersOf<typeof layers>;\n * const handler = createCollisionPairHandler<ECS, Layer>({\n * 'player:enemy': (playerId, enemyId, ecs) => { ... },\n * });\n *\n * ecs.eventBus.subscribe('collision', (data) => handler({ data, ecs }));\n * ```\n */\nexport function createCollisionPairHandler<W = unknown, L extends string = string>(\n\tpairs: { [K in `${L}:${L}`]?: CollisionPairCallback<W> }\n): (ctx: { data: CollisionEvent<L>; ecs: W }) => void;\nexport function createCollisionPairHandler<W = unknown>(\n\tpairs: Record<string, CollisionPairCallback<W> | undefined>\n): (ctx: { data: CollisionEvent<string>; ecs: W }) => void {\n\tconst lookup = new Map<string, PairEntry<W>>();\n\tconst explicitKeys = new Set<string>();\n\n\t// First pass: collect all explicit keys\n\tfor (const key of Object.keys(pairs)) {\n\t\tparsePairKey(key); // validate\n\t\texplicitKeys.add(key);\n\t}\n\n\t// Second pass: build lookup with forward + conditional reverse entries\n\tfor (const key of Object.keys(pairs)) {\n\t\tconst [layerA, layerB] = parsePairKey(key);\n\t\tconst callback = pairs[key];\n\t\tif (!callback) continue;\n\n\t\t// Forward entry\n\t\tlookup.set(key, { callback, swapped: false });\n\n\t\t// Reverse entry (only if the reverse key wasn't explicitly registered\n\t\t// and it's not a self-collision where forward === reverse)\n\t\tconst reverseKey = `${layerB}:${layerA}`;\n\t\tif (reverseKey !== key && !explicitKeys.has(reverseKey)) {\n\t\t\tlookup.set(reverseKey, { callback, swapped: true });\n\t\t}\n\t}\n\n\treturn function collisionPairDispatch({ data: event, ecs }: { data: CollisionEvent<string>; ecs: W }): void {\n\t\tconst entry = lookup.get(event.layerA + ':' + event.layerB);\n\t\tif (!entry) return;\n\n\t\tif (entry.swapped) {\n\t\t\tentry.callback(event.entityB, event.entityA, ecs);\n\t\t} else {\n\t\t\tentry.callback(event.entityA, event.entityB, ecs);\n\t\t}\n\t};\n}\n\n// ==================== Dependency Types ====================\n\n// ==================== Module-level Collision Callback ====================\n\ninterface CollisionEventBus<L extends string> {\n\tpublish(event: 'collision', data: CollisionEvent<L>): void;\n}\n\n/**\n * Module-level reusable collision event. Subscribers must consume\n * synchronously — same contract as the shared narrowphase Contact.\n */\nconst _collisionEvent: CollisionEvent<string> = {\n\tentityA: 0, entityB: 0, layerA: '', layerB: '',\n\tnormalX: 0, normalY: 0, depth: 0,\n};\n\nfunction onCollisionDetected<L extends string>(\n\ta: BaseColliderInfo<L>,\n\tb: BaseColliderInfo<L>,\n\tcontact: Contact,\n\teventBus: CollisionEventBus<L>,\n): void {\n\t_collisionEvent.entityA = a.entityId;\n\t_collisionEvent.entityB = b.entityId;\n\t_collisionEvent.layerA = a.layer;\n\t_collisionEvent.layerB = b.layer;\n\t_collisionEvent.normalX = contact.normalX;\n\t_collisionEvent.normalY = contact.normalY;\n\t_collisionEvent.depth = contact.depth;\n\teventBus.publish('collision', _collisionEvent as CollisionEvent<L>);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a collision plugin for ECSpresso.\n *\n * This plugin provides:\n * - Collision detection between entities with colliders\n * - AABB-AABB, circle-circle, and AABB-circle collision\n * - Layer-based filtering for collision pairs\n * - Deduplication of A-B / B-A collisions\n * - Automatic broadphase acceleration when spatialIndex resource is present\n *\n * Uses worldTransform for position (world-space collision detection).\n * The `layers` parameter is required for type inference — at runtime the\n * plugin does not consume it.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * const ecs = ECSpresso\n * .create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createCollisionPlugin({ layers }))\n * .build();\n *\n * // Entity with collision\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createAABBCollider(50, 30),\n * ...layers.player(),\n * });\n * ```\n */\nexport function createCollisionPlugin<L extends string, G extends string = 'physics'>(\n\toptions: CollisionPluginOptions<G> & { layers: LayerFactories<Record<L, readonly string[]>> }\n) {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options;\n\n\treturn definePlugin('collision')\n\t\t.withComponentTypes<CollisionComponentTypes<L>>()\n\t\t.withEventTypes<CollisionEventTypes<L>>()\n\t\t.withLabels<'collision-detection'>()\n\t\t.withGroups<G>()\n\t\t.requires<TransformWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// Grow-only pool of BaseColliderInfo slots reused across frames.\n\t\t\t// Steady-state: zero allocations per frame once the pool is warm.\n\t\t\tconst colliderPool: BaseColliderInfo<L>[] = [];\n\t\t\tconst broadphaseScratch = createBroadphaseScratch<BaseColliderInfo<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tconst ensureSlot = (idx: number, entityId: number, layer: L, collidesWith: readonly L[]): BaseColliderInfo<L> => {\n\t\t\t\tlet slot = colliderPool[idx];\n\t\t\t\tif (!slot) {\n\t\t\t\t\tslot = {\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\tcollidesWith,\n\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\tshape: AABB_SHAPE,\n\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t};\n\t\t\t\t\tcolliderPool[idx] = slot;\n\t\t\t\t}\n\t\t\t\treturn slot;\n\t\t\t};\n\n\t\t\tworld\n\t\t\t\t.addSystem('collision-detection')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('aabbOnly', {\n\t\t\t\t\twith: ['worldTransform', 'collisionLayer', 'aabbCollider'],\n\t\t\t\t\twithout: ['circleCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('circleOnly', {\n\t\t\t\t\twith: ['worldTransform', 'collisionLayer', 'circleCollider'],\n\t\t\t\t\twithout: ['aabbCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('both', {\n\t\t\t\t\twith: ['worldTransform', 'collisionLayer', 'aabbCollider', 'circleCollider'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\tfor (const entity of queries.aabbOnly) {\n\t\t\t\t\t\tconst { worldTransform, collisionLayer, aabbCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform.x, worldTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabbCollider, undefined,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.circleOnly) {\n\t\t\t\t\t\tconst { worldTransform, collisionLayer, circleCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform.x, worldTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\tundefined, circleCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// fillBaseColliderInfo picks aabb when both present — preserves prior precedence.\n\t\t\t\t\tfor (const entity of queries.both) {\n\t\t\t\t\t\tconst { worldTransform, collisionLayer, aabbCollider, circleCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform.x, worldTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabbCollider, circleCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex>('spatialIndex');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions(colliderPool, count, broadphaseScratch, cachedSI, onCollisionDetected<L>, ecs.eventBus);\n\t\t\t\t});\n\t\t});\n}\n\n",
6
6
  "/**\n * Lazy monotonic registry mapping layer name → unique bit. Lets pair\n * filtering use a single `(a.collidesWithMask & b.layerBit)` check\n * instead of `Array.includes` on every collision pair.\n *\n * One registry per dimension (2D and 3D) — user-defined layer namespaces\n * are independent, so bits should not be shared across systems.\n *\n * Maximum 32 layers per registry (one per bit in a 32-bit signed int).\n * Crossing the limit throws on the next `getLayerBit` call.\n */\nexport interface LayerBitRegistry {\n\tgetLayerBit(layer: string): number;\n\t/** OR of `getLayerBit` for every entry. Cached by array reference. */\n\tgetCollidesWithMask(collidesWith: readonly string[]): number;\n}\n\nexport function createLayerBitRegistry(label: string): LayerBitRegistry {\n\tconst layerBits = new Map<string, number>();\n\tconst maskCache = new WeakMap<readonly string[], number>();\n\tlet nextBit = 1;\n\n\tfunction getLayerBit(layer: string): number {\n\t\tconst existing = layerBits.get(layer);\n\t\tif (existing !== undefined) return existing;\n\t\tif (nextBit === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`[ecspresso] ${label} layer bitmask overflow: more than 32 distinct layers registered`,\n\t\t\t);\n\t\t}\n\t\tconst bit = nextBit;\n\t\tlayerBits.set(layer, bit);\n\t\t// `<<= 1` rolls 1<<31 to 0, which is detected on the next call.\n\t\tnextBit <<= 1;\n\t\treturn bit;\n\t}\n\n\tfunction getCollidesWithMask(collidesWith: readonly string[]): number {\n\t\tconst cached = maskCache.get(collidesWith);\n\t\tif (cached !== undefined) return cached;\n\t\tlet mask = 0;\n\t\tfor (let i = 0; i < collidesWith.length; i++) {\n\t\t\tmask |= getLayerBit(collidesWith[i]!);\n\t\t}\n\t\tmaskCache.set(collidesWith, mask);\n\t\treturn mask;\n\t}\n\n\treturn { getLayerBit, getCollidesWithMask };\n}\n",
7
7
  "/**\n * Shared Narrowphase Module\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline used by both the collision plugin (event-only) and\n * the physics2D plugin (impulse response).\n */\n\nimport type { SpatialIndex } from './spatial-hash';\nimport { createLayerBitRegistry } from './layer-bit-registry';\n\n// ==================== Contact ====================\n\n/**\n * Contact result from a narrowphase test. Normal points from A toward B.\n *\n * Narrowphase functions use this as an out-parameter: the caller owns the\n * struct, the function writes fields in place and returns `true` on hit.\n * The `onContact` callback in `detectCollisions` receives a shared module-\n * level instance — **subscribers must consume it synchronously and must not\n * retain the reference across frames**.\n */\nexport interface Contact {\n\tnormalX: number;\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Module-level reusable Contact passed down from `detectCollisions` into\n * narrowphase tests and forwarded to the `onContact` callback. Reused across\n * every pair in every frame — zero allocation in the narrowphase hot path.\n */\nconst _sharedContact: Contact = { normalX: 0, normalY: 0, depth: 0 };\n\n// ==================== BaseColliderInfo ====================\n\n/** Collider shape discriminator for the flattened BaseColliderInfo layout. */\nexport const AABB_SHAPE = 0;\nexport const CIRCLE_SHAPE = 1;\nexport type ColliderShape = typeof AABB_SHAPE | typeof CIRCLE_SHAPE;\n\n/**\n * Minimum collider data shared by collision and physics bundles.\n *\n * Flat layout (no nested `aabb` / `circle` sub-objects): the `shape`\n * discriminator tells you whether to read `halfWidth`/`halfHeight`\n * (AABB) or `radius` (Circle). Unused fields are set to 0.\n *\n * This shape is pool-friendly — all fields are assigned in place each\n * frame without allocating nested objects.\n */\nexport interface BaseColliderInfo<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\t/**\n\t * Bit assigned to `layer` from the lazy layer registry. Populated by\n\t * `fillBaseColliderInfo`. Used together with `collidesWithMask` to\n\t * replace per-pair `Array.includes` layer checks with a single AND.\n\t */\n\tlayerBit: number;\n\t/** OR of `getLayerBit` for every entry in `collidesWith`. */\n\tcollidesWithMask: number;\n\tshape: ColliderShape;\n\thalfWidth: number;\n\thalfHeight: number;\n\tradius: number;\n}\n\n// ==================== Layer Bit Registry ====================\n\nconst _layerRegistry = createLayerBitRegistry('Collision');\nexport const getLayerBit = _layerRegistry.getLayerBit;\nexport const getCollidesWithMask = _layerRegistry.getCollidesWithMask;\n\n// ==================== Collider Construction ====================\n\n/**\n * Populate a `BaseColliderInfo` slot in place from raw component data.\n * Returns `true` if the slot was filled, `false` if the entity has no\n * collider (caller should skip it).\n *\n * If an entity has both AABB and circle colliders, AABB wins and only\n * the AABB offset is applied. This matches the dispatch precedence in\n * `computeContact`; the previous implementation stacked both offsets,\n * which was a bug.\n */\nexport function fillBaseColliderInfo<L extends string>(\n\tinfo: BaseColliderInfo<L>,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb: { width: number; height: number; offsetX?: number; offsetY?: number } | undefined,\n\tcircle: { radius: number; offsetX?: number; offsetY?: number } | undefined,\n): boolean {\n\tinfo.entityId = entityId;\n\tinfo.layer = layer;\n\tinfo.collidesWith = collidesWith;\n\tinfo.layerBit = getLayerBit(layer);\n\tinfo.collidesWithMask = getCollidesWithMask(collidesWith);\n\n\tif (aabb) {\n\t\tinfo.x = x + (aabb.offsetX ?? 0);\n\t\tinfo.y = y + (aabb.offsetY ?? 0);\n\t\tinfo.shape = AABB_SHAPE;\n\t\tinfo.halfWidth = aabb.width / 2;\n\t\tinfo.halfHeight = aabb.height / 2;\n\t\tinfo.radius = 0;\n\t\treturn true;\n\t}\n\n\tif (circle) {\n\t\tinfo.x = x + (circle.offsetX ?? 0);\n\t\tinfo.y = y + (circle.offsetY ?? 0);\n\t\tinfo.shape = CIRCLE_SHAPE;\n\t\tinfo.halfWidth = 0;\n\t\tinfo.halfHeight = 0;\n\t\tinfo.radius = circle.radius;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex resource, returning undefined when absent.\n * Centralizes the cross-plugin typed lookup so individual plugins don't each\n * need to import SpatialIndex or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex | undefined {\n\treturn tryGetResource<SpatialIndex>('spatialIndex');\n}\n\n// ==================== Narrowphase Tests ====================\n\n/**\n * Write an AABB-AABB contact into `out`. Returns `true` if the shapes\n * overlap (out was filled), `false` otherwise.\n */\nexport function computeAABBvsAABB(\n\tax: number, ay: number, ahw: number, ahh: number,\n\tbx: number, by: number, bhw: number, bhh: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\n\tif (overlapX <= 0 || overlapY <= 0) return false;\n\n\tif (overlapX < overlapY) {\n\t\tout.normalX = dx >= 0 ? 1 : -1;\n\t\tout.normalY = 0;\n\t\tout.depth = overlapX;\n\t\treturn true;\n\t}\n\tout.normalX = 0;\n\tout.normalY = dy >= 0 ? 1 : -1;\n\tout.depth = overlapY;\n\treturn true;\n}\n\nexport function computeCircleVsCircle(\n\tax: number, ay: number, ar: number,\n\tbx: number, by: number, br: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return false;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\tout.normalX = 1;\n\t\tout.normalY = 0;\n\t\tout.depth = radiusSum;\n\t\treturn true;\n\t}\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radiusSum - dist;\n\treturn true;\n}\n\nexport function computeAABBvsCircle(\n\taabbX: number, aabbY: number, ahw: number, ahh: number,\n\tcircleX: number, circleY: number, radius: number,\n\tout: Contact,\n): boolean {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(circleX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(circleY, aabbY + ahh));\n\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\tif (distSq >= radius * radius) return false;\n\n\t// Circle center inside AABB\n\tif (distSq === 0) {\n\t\tconst pushLeft = (circleX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - circleX);\n\t\tconst pushUp = (circleY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - circleY);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown);\n\n\t\tif (minPush === pushRight) {\n\t\t\tout.normalX = 1; out.normalY = 0; out.depth = pushRight + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushLeft) {\n\t\t\tout.normalX = -1; out.normalY = 0; out.depth = pushLeft + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushDown) {\n\t\t\tout.normalX = 0; out.normalY = 1; out.depth = pushDown + radius;\n\t\t\treturn true;\n\t\t}\n\t\tout.normalX = 0; out.normalY = -1; out.depth = pushUp + radius;\n\t\treturn true;\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radius - dist;\n\treturn true;\n}\n\n// ==================== Contact Dispatcher ====================\n\n/**\n * Dispatch to the correct narrowphase function for the given pair and\n * write the contact into `out`. Returns `true` if the pair overlaps.\n */\nexport function computeContact(a: BaseColliderInfo, b: BaseColliderInfo, out: Contact): boolean {\n\tif (a.shape === AABB_SHAPE && b.shape === AABB_SHAPE) {\n\t\treturn computeAABBvsAABB(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === CIRCLE_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeCircleVsCircle(\n\t\t\ta.x, a.y, a.radius,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === AABB_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeAABBvsCircle(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\t// a is Circle, b is AABB — compute as AABB-vs-Circle, then flip normal in place\n\tif (!computeAABBvsCircle(\n\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\ta.x, a.y, a.radius,\n\t\tout,\n\t)) return false;\n\tout.normalX = -out.normalX;\n\tout.normalY = -out.normalY;\n\treturn true;\n}\n\n// ==================== Collision Iteration ====================\n\nconst _broadphaseCandidates: number[] = [];\n\n/**\n * Per-caller scratch for the broadphase entityId → collider lookup.\n *\n * Dense `arr` indexed by entityId, paired with a `gen` stamp array that marks\n * which slots are live this call. Bumping `current` invalidates all prior\n * entries without clearing — replaces the per-frame `Map.clear()` + N\n * `Map.set()` allocation churn that a `Map<number, I>` would incur.\n *\n * Owned per plugin instance (alongside its `colliderPool`), so concurrent\n * worlds don't share state and `I` stays fully typed without erasure.\n */\nexport interface BroadphaseScratch<I extends BaseColliderInfo> {\n\tarr: (I | undefined)[];\n\tgen: number[];\n\tcurrent: number;\n}\n\nexport function createBroadphaseScratch<I extends BaseColliderInfo>(): BroadphaseScratch<I> {\n\treturn { arr: [], gen: [], current: 0 };\n}\n\nlet _bruteForceWarned = false;\nconst BRUTE_FORCE_WARN_THRESHOLD = 50;\n\n/**\n * Generic collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * `count` is the number of live entries at the front of `colliders`.\n * The array itself may be a grow-only pool — only indices `[0, count)`\n * are iterated, so trailing pool slots are ignored.\n *\n * `scratch` is a caller-owned `BroadphaseScratch<I>` used by the broadphase\n * path as an entityId → collider lookup. Allocate it once per plugin instance\n * and pass the same reference every call.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tscratch: BroadphaseScratch<I>,\n\tspatialIndex: SpatialIndex | undefined,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, count, scratch, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, count, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (!_bruteForceWarned && count >= BRUTE_FORCE_WARN_THRESHOLD) {\n\t\t_bruteForceWarned = true;\n\t\tconsole.warn(\n\t\t\t`[ecspresso] Collision detection is using O(n²) brute force with ${count} colliders. ` +\n\t\t\t`For better performance, install createSpatialIndexPlugin() alongside your collision or physics2D plugin.`,\n\t\t);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tfor (let j = i + 1; j < count; j++) {\n\t\t\tconst b = colliders[j];\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tscratch: BroadphaseScratch<I>,\n\tspatialIndex: SpatialIndex,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tconst arr = scratch.arr;\n\tconst stamps = scratch.gen;\n\tconst gen = ++scratch.current;\n\tfor (let i = 0; i < count; i++) {\n\t\tconst c = colliders[i];\n\t\tif (!c) continue;\n\t\tconst id = c.entityId;\n\t\tarr[id] = c;\n\t\tstamps[id] = gen;\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tconst aHalfW = a.shape === AABB_SHAPE ? a.halfWidth : a.radius;\n\t\tconst aHalfH = a.shape === AABB_SHAPE ? a.halfHeight : a.radius;\n\n\t\t_broadphaseCandidates.length = 0;\n\t\tspatialIndex.queryRectInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH,\n\t\t\ta.x + aHalfW, a.y + aHalfH,\n\t\t\t_broadphaseCandidates,\n\t\t\ta.entityId,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tif (stamps[bId] !== gen) continue;\n\t\t\tconst b = arr[bId];\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n",
8
- "/**\n * Collision 3D Plugin for ECSpresso\n *\n * Provides layer-based 3D collision detection with events.\n * Uses worldTransform3D for position (world-space collision).\n * Supports AABB3D and sphere colliders.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { Transform3DWorldConfig } from '../spatial/transform3D';\nimport {\n\tfillBaseColliderInfo3D,\n\tdetectCollisions3D,\n\tAABB3D_SHAPE,\n\ttype Contact3D,\n\ttype BaseColliderInfo3D,\n} from '../../utils/narrowphase3D';\nimport type { SpatialIndex3D } from '../../utils/spatial-hash3D';\nimport {\n\tdefineCollisionLayers,\n\tcreateCollisionPairHandler,\n\tcreateCollisionLayer,\n\ttype CollisionLayer,\n\ttype LayerFactories,\n\ttype LayersOf,\n\ttype CollisionPairCallback,\n} from './collision';\n\n// Re-export dimension-agnostic layer utilities so consumers only need one import\nexport { defineCollisionLayers, createCollisionPairHandler, createCollisionLayer };\nexport type { CollisionLayer, LayerFactories, LayersOf, CollisionPairCallback };\n\n// Re-export collider shapes (defined in spatial-index3D to avoid duplication)\nexport type { AABB3DCollider, SphereCollider } from '../spatial/spatial-index3D';\nimport type { AABB3DCollider, SphereCollider } from '../spatial/spatial-index3D';\n\n// ==================== Component Types ====================\n\n/**\n * Component types provided by the collision3D plugin.\n */\nexport interface Collision3DComponentTypes<L extends string = never> {\n\taabb3DCollider: AABB3DCollider;\n\tsphereCollider: SphereCollider;\n\tcollisionLayer: CollisionLayer<L>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two 3D entities collide.\n *\n * Normal components are flattened to avoid per-event allocation in the hot path.\n */\nexport interface Collision3DEvent<L extends string = never> {\n\tentityA: number;\n\tentityB: number;\n\tlayerA: L;\n\tlayerB: L;\n\t/** Contact normal X, pointing from entityA toward entityB */\n\tnormalX: number;\n\t/** Contact normal Y, pointing from entityA toward entityB */\n\tnormalY: number;\n\t/** Contact normal Z, pointing from entityA toward entityB */\n\tnormalZ: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Event types provided by the collision3D plugin.\n */\nexport interface Collision3DEventTypes<L extends string = never> {\n\tcollision3D: Collision3DEvent<L>;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the collision3D plugin.\n */\nexport interface Collision3DPluginOptions<G extends string = 'physics'> extends BasePluginOptions<G> {}\n\n// ==================== Helper Functions ====================\n\nexport function createAABB3DCollider(\n\twidth: number,\n\theight: number,\n\tdepth: number,\n\toffsetX?: number,\n\toffsetY?: number,\n\toffsetZ?: number,\n): { aabb3DCollider: AABB3DCollider } {\n\tconst collider: AABB3DCollider = { width, height, depth };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\tif (offsetZ !== undefined) collider.offsetZ = offsetZ;\n\treturn { aabb3DCollider: collider };\n}\n\nexport function createSphereCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number,\n\toffsetZ?: number,\n): { sphereCollider: SphereCollider } {\n\tconst collider: SphereCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\tif (offsetZ !== undefined) collider.offsetZ = offsetZ;\n\treturn { sphereCollider: collider };\n}\n\n// ==================== Module-level Collision Callback ====================\n\ninterface Collision3DEventBus<L extends string> {\n\tpublish(event: 'collision3D', data: Collision3DEvent<L>): void;\n}\n\n/**\n * Module-level reusable collision event. Subscribers must consume synchronously —\n * same contract as the shared narrowphase Contact3D.\n */\nconst _collisionEvent: Collision3DEvent<string> = {\n\tentityA: 0, entityB: 0, layerA: '', layerB: '',\n\tnormalX: 0, normalY: 0, normalZ: 0, depth: 0,\n};\n\nfunction onCollisionDetected3D<L extends string>(\n\ta: BaseColliderInfo3D<L>,\n\tb: BaseColliderInfo3D<L>,\n\tcontact: Contact3D,\n\teventBus: Collision3DEventBus<L>,\n): void {\n\t_collisionEvent.entityA = a.entityId;\n\t_collisionEvent.entityB = b.entityId;\n\t_collisionEvent.layerA = a.layer;\n\t_collisionEvent.layerB = b.layer;\n\t_collisionEvent.normalX = contact.normalX;\n\t_collisionEvent.normalY = contact.normalY;\n\t_collisionEvent.normalZ = contact.normalZ;\n\t_collisionEvent.depth = contact.depth;\n\teventBus.publish('collision3D', _collisionEvent as Collision3DEvent<L>);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 3D collision plugin for ECSpresso.\n *\n * Provides layer-based collision detection between entities with 3D colliders,\n * publishing `collision3D` events on contact. Supports AABB3D-AABB3D,\n * sphere-sphere, and AABB3D-sphere tests. Automatically uses the\n * `spatialIndex3D` resource for broadphase when present.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * const ecs = ECSpresso\n * .create()\n * .withPlugin(createTransform3DPlugin())\n * .withPlugin(createCollision3DPlugin({ layers }))\n * .build();\n *\n * ecs.eventBus.subscribe('collision3D', (data) => {\n * console.log(data.entityA, data.entityB, data.normalZ);\n * });\n * ```\n */\nexport function createCollision3DPlugin<L extends string, G extends string = 'physics'>(\n\toptions: Collision3DPluginOptions<G> & { layers: LayerFactories<Record<L, readonly string[]>> },\n) {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options;\n\n\treturn definePlugin('collision3D')\n\t\t.withComponentTypes<Collision3DComponentTypes<L>>()\n\t\t.withEventTypes<Collision3DEventTypes<L>>()\n\t\t.withLabels<'collision3D-detection'>()\n\t\t.withGroups<G>()\n\t\t.requires<Transform3DWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// Grow-only pool of BaseColliderInfo3D slots reused across frames.\n\t\t\tconst colliderPool: BaseColliderInfo3D<L>[] = [];\n\t\t\t// Reusable entityId → collider lookup for the broadphase path.\n\t\t\tconst broadphaseMap = new Map<number, BaseColliderInfo3D<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex3D | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tworld\n\t\t\t\t.addSystem('collision3D-detection')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('collidables', {\n\t\t\t\t\twith: ['worldTransform3D', 'collisionLayer'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\t// TODO(perf): collider shape is discovered via two ecs.getComponent\n\t\t\t\t\t// calls per entity per frame because the query can't express\n\t\t\t\t\t// \"aabb3DCollider OR sphereCollider\". Splitting into two queries\n\t\t\t\t\t// (aabb-bearing, sphere-bearing) would eliminate these lookups at\n\t\t\t\t\t// the cost of two pool-fill passes. Keep in sync with physics3D.\n\t\t\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\t\t\tconst { worldTransform3D, collisionLayer } = entity.components;\n\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabb3DCollider');\n\t\t\t\t\t\tconst sphere = aabb ? undefined : ecs.getComponent(entity.id, 'sphereCollider');\n\t\t\t\t\t\tif (!aabb && !sphere) continue;\n\n\t\t\t\t\t\tlet slot = colliderPool[count];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\tslot = {\n\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t\tx: worldTransform3D.x,\n\t\t\t\t\t\t\t\ty: worldTransform3D.y,\n\t\t\t\t\t\t\t\tz: worldTransform3D.z,\n\t\t\t\t\t\t\t\tlayer: collisionLayer.layer,\n\t\t\t\t\t\t\t\tcollidesWith: collisionLayer.collidesWith,\n\t\t\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\t\t\tshape: AABB3D_SHAPE,\n\t\t\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\t\t\thalfDepth: 0,\n\t\t\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcolliderPool[count] = slot;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!fillBaseColliderInfo3D(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform3D.x, worldTransform3D.y, worldTransform3D.z,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb, sphere,\n\t\t\t\t\t\t)) continue;\n\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex3D>('spatialIndex3D');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions3D(colliderPool, count, broadphaseMap, cachedSI, onCollisionDetected3D<L>, ecs.eventBus);\n\t\t\t\t});\n\t\t});\n}\n",
8
+ "/**\n * Collision 3D Plugin for ECSpresso\n *\n * Provides layer-based 3D collision detection with events.\n * Uses worldTransform3D for position (world-space collision).\n * Supports AABB3D and sphere colliders.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\nimport type { Transform3DWorldConfig } from '../spatial/transform3D';\nimport {\n\tfillBaseColliderInfo3D,\n\tdetectCollisions3D,\n\tAABB3D_SHAPE,\n\ttype Contact3D,\n\ttype BaseColliderInfo3D,\n} from '../../utils/narrowphase3D';\nimport type { SpatialIndex3D } from '../../utils/spatial-hash3D';\nimport {\n\tdefineCollisionLayers,\n\tcreateCollisionPairHandler,\n\tcreateCollisionLayer,\n\ttype CollisionLayer,\n\ttype LayerFactories,\n\ttype LayersOf,\n\ttype CollisionPairCallback,\n} from './collision';\n\n// Re-export dimension-agnostic layer utilities so consumers only need one import\nexport { defineCollisionLayers, createCollisionPairHandler, createCollisionLayer };\nexport type { CollisionLayer, LayerFactories, LayersOf, CollisionPairCallback };\n\n// Re-export collider shapes (defined in spatial-index3D to avoid duplication)\nexport type { AABB3DCollider, SphereCollider } from '../spatial/spatial-index3D';\nimport type { AABB3DCollider, SphereCollider } from '../spatial/spatial-index3D';\n\n// ==================== Component Types ====================\n\n/**\n * Component types provided by the collision3D plugin.\n */\nexport interface Collision3DComponentTypes<L extends string = never> {\n\taabb3DCollider: AABB3DCollider;\n\tsphereCollider: SphereCollider;\n\tcollisionLayer: CollisionLayer<L>;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event fired when two 3D entities collide.\n *\n * Normal components are flattened to avoid per-event allocation in the hot path.\n */\nexport interface Collision3DEvent<L extends string = never> {\n\tentityA: number;\n\tentityB: number;\n\tlayerA: L;\n\tlayerB: L;\n\t/** Contact normal X, pointing from entityA toward entityB */\n\tnormalX: number;\n\t/** Contact normal Y, pointing from entityA toward entityB */\n\tnormalY: number;\n\t/** Contact normal Z, pointing from entityA toward entityB */\n\tnormalZ: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Event types provided by the collision3D plugin.\n */\nexport interface Collision3DEventTypes<L extends string = never> {\n\tcollision3D: Collision3DEvent<L>;\n}\n\n// ==================== Plugin Options ====================\n\n/**\n * Configuration options for the collision3D plugin.\n */\nexport interface Collision3DPluginOptions<G extends string = 'physics'> extends BasePluginOptions<G> {}\n\n// ==================== Helper Functions ====================\n\nexport function createAABB3DCollider(\n\twidth: number,\n\theight: number,\n\tdepth: number,\n\toffsetX?: number,\n\toffsetY?: number,\n\toffsetZ?: number,\n): { aabb3DCollider: AABB3DCollider } {\n\tconst collider: AABB3DCollider = { width, height, depth };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\tif (offsetZ !== undefined) collider.offsetZ = offsetZ;\n\treturn { aabb3DCollider: collider };\n}\n\nexport function createSphereCollider(\n\tradius: number,\n\toffsetX?: number,\n\toffsetY?: number,\n\toffsetZ?: number,\n): { sphereCollider: SphereCollider } {\n\tconst collider: SphereCollider = { radius };\n\tif (offsetX !== undefined) collider.offsetX = offsetX;\n\tif (offsetY !== undefined) collider.offsetY = offsetY;\n\tif (offsetZ !== undefined) collider.offsetZ = offsetZ;\n\treturn { sphereCollider: collider };\n}\n\n// ==================== Module-level Collision Callback ====================\n\ninterface Collision3DEventBus<L extends string> {\n\tpublish(event: 'collision3D', data: Collision3DEvent<L>): void;\n}\n\n/**\n * Module-level reusable collision event. Subscribers must consume synchronously —\n * same contract as the shared narrowphase Contact3D.\n */\nconst _collisionEvent: Collision3DEvent<string> = {\n\tentityA: 0, entityB: 0, layerA: '', layerB: '',\n\tnormalX: 0, normalY: 0, normalZ: 0, depth: 0,\n};\n\nfunction onCollisionDetected3D<L extends string>(\n\ta: BaseColliderInfo3D<L>,\n\tb: BaseColliderInfo3D<L>,\n\tcontact: Contact3D,\n\teventBus: Collision3DEventBus<L>,\n): void {\n\t_collisionEvent.entityA = a.entityId;\n\t_collisionEvent.entityB = b.entityId;\n\t_collisionEvent.layerA = a.layer;\n\t_collisionEvent.layerB = b.layer;\n\t_collisionEvent.normalX = contact.normalX;\n\t_collisionEvent.normalY = contact.normalY;\n\t_collisionEvent.normalZ = contact.normalZ;\n\t_collisionEvent.depth = contact.depth;\n\teventBus.publish('collision3D', _collisionEvent as Collision3DEvent<L>);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 3D collision plugin for ECSpresso.\n *\n * Provides layer-based collision detection between entities with 3D colliders,\n * publishing `collision3D` events on contact. Supports AABB3D-AABB3D,\n * sphere-sphere, and AABB3D-sphere tests. Automatically uses the\n * `spatialIndex3D` resource for broadphase when present.\n *\n * @example\n * ```typescript\n * const layers = defineCollisionLayers({ player: ['enemy'], enemy: ['player'] });\n * const ecs = ECSpresso\n * .create()\n * .withPlugin(createTransform3DPlugin())\n * .withPlugin(createCollision3DPlugin({ layers }))\n * .build();\n *\n * ecs.eventBus.subscribe('collision3D', (data) => {\n * console.log(data.entityA, data.entityB, data.normalZ);\n * });\n * ```\n */\nexport function createCollision3DPlugin<L extends string, G extends string = 'physics'>(\n\toptions: Collision3DPluginOptions<G> & { layers: LayerFactories<Record<L, readonly string[]>> },\n) {\n\tconst {\n\t\tsystemGroup = 'physics',\n\t\tpriority = 0,\n\t\tphase = 'postUpdate',\n\t} = options;\n\n\treturn definePlugin('collision3D')\n\t\t.withComponentTypes<Collision3DComponentTypes<L>>()\n\t\t.withEventTypes<Collision3DEventTypes<L>>()\n\t\t.withLabels<'collision3D-detection'>()\n\t\t.withGroups<G>()\n\t\t.requires<Transform3DWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// Grow-only pool of BaseColliderInfo3D slots reused across frames.\n\t\t\tconst colliderPool: BaseColliderInfo3D<L>[] = [];\n\t\t\t// Reusable entityId → collider lookup for the broadphase path.\n\t\t\tconst broadphaseMap = new Map<number, BaseColliderInfo3D<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex3D | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tconst ensureSlot = (idx: number, entityId: number, layer: L, collidesWith: readonly L[]): BaseColliderInfo3D<L> => {\n\t\t\t\tlet slot = colliderPool[idx];\n\t\t\t\tif (!slot) {\n\t\t\t\t\tslot = {\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\tz: 0,\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\tcollidesWith,\n\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\tshape: AABB3D_SHAPE,\n\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\thalfDepth: 0,\n\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t};\n\t\t\t\t\tcolliderPool[idx] = slot;\n\t\t\t\t}\n\t\t\t\treturn slot;\n\t\t\t};\n\n\t\t\tworld\n\t\t\t\t.addSystem('collision3D-detection')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('aabbOnly', {\n\t\t\t\t\twith: ['worldTransform3D', 'collisionLayer', 'aabb3DCollider'],\n\t\t\t\t\twithout: ['sphereCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('sphereOnly', {\n\t\t\t\t\twith: ['worldTransform3D', 'collisionLayer', 'sphereCollider'],\n\t\t\t\t\twithout: ['aabb3DCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('both', {\n\t\t\t\t\twith: ['worldTransform3D', 'collisionLayer', 'aabb3DCollider', 'sphereCollider'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\tfor (const entity of queries.aabbOnly) {\n\t\t\t\t\t\tconst { worldTransform3D, collisionLayer, aabb3DCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo3D(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform3D.x, worldTransform3D.y, worldTransform3D.z,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb3DCollider, undefined,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.sphereOnly) {\n\t\t\t\t\t\tconst { worldTransform3D, collisionLayer, sphereCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo3D(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform3D.x, worldTransform3D.y, worldTransform3D.z,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\tundefined, sphereCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// fillBaseColliderInfo3D picks aabb when both present — preserves prior precedence.\n\t\t\t\t\tfor (const entity of queries.both) {\n\t\t\t\t\t\tconst { worldTransform3D, collisionLayer, aabb3DCollider, sphereCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith);\n\t\t\t\t\t\tif (!fillBaseColliderInfo3D(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, worldTransform3D.x, worldTransform3D.y, worldTransform3D.z,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb3DCollider, sphereCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex3D>('spatialIndex3D');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions3D(colliderPool, count, broadphaseMap, cachedSI, onCollisionDetected3D<L>, ecs.eventBus);\n\t\t\t\t});\n\t\t});\n}\n",
9
9
  "/**\n * Shared Narrowphase Module — 3D\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline for 3D collider pairs (AABB3D + Sphere).\n *\n * Mirrors the 2D narrowphase (`narrowphase.ts`) with an added Z axis.\n */\n\nimport type { SpatialIndex3D } from './spatial-hash3D';\nimport { createLayerBitRegistry } from './layer-bit-registry';\n\n// ==================== Contact3D ====================\n\n/**\n * Contact result from a 3D narrowphase test. Normal points from A toward B.\n *\n * Narrowphase functions use this as an out-parameter: the caller owns the\n * struct, the function writes fields in place and returns `true` on hit.\n * The `onContact` callback in `detectCollisions3D` receives a shared module-\n * level instance — **subscribers must consume it synchronously and must not\n * retain the reference across frames**.\n */\nexport interface Contact3D {\n\tnormalX: number;\n\tnormalY: number;\n\tnormalZ: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Module-level reusable Contact3D passed down from `detectCollisions3D` into\n * narrowphase tests and forwarded to the `onContact` callback. Reused across\n * every pair in every frame — zero allocation in the narrowphase hot path.\n */\nconst _sharedContact: Contact3D = { normalX: 0, normalY: 0, normalZ: 0, depth: 0 };\n\n// ==================== BaseColliderInfo3D ====================\n\n/** Collider shape discriminator for the flattened BaseColliderInfo3D layout. */\nexport const AABB3D_SHAPE = 0;\nexport const SPHERE_SHAPE = 1;\nexport type ColliderShape3D = typeof AABB3D_SHAPE | typeof SPHERE_SHAPE;\n\n/**\n * Minimum collider data shared by 3D collision and physics bundles.\n *\n * Flat layout (no nested sub-objects): the `shape` discriminator tells you\n * whether to read `halfWidth`/`halfHeight`/`halfDepth` (AABB3D) or `radius`\n * (Sphere). Unused fields are set to 0.\n *\n * Pool-friendly — all fields are assigned in place each frame.\n */\nexport interface BaseColliderInfo3D<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tz: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\t/**\n\t * Bit assigned to `layer` from the lazy layer registry. Populated by\n\t * `fillBaseColliderInfo3D`. Used together with `collidesWithMask` to\n\t * replace per-pair `Array.includes` layer checks with a single AND.\n\t */\n\tlayerBit: number;\n\t/** OR of `getLayerBit3D` for every entry in `collidesWith`. */\n\tcollidesWithMask: number;\n\tshape: ColliderShape3D;\n\thalfWidth: number;\n\thalfHeight: number;\n\thalfDepth: number;\n\tradius: number;\n}\n\n// ==================== Layer Bit Registry ====================\n\n// Independent from the 2D registry: 2D and 3D layer namespaces are\n// defined separately by user code, so bits should not collide.\nconst _layerRegistry = createLayerBitRegistry('3D collision');\nexport const getLayerBit3D = _layerRegistry.getLayerBit;\nexport const getCollidesWithMask3D = _layerRegistry.getCollidesWithMask;\n\n// ==================== Collider Construction ====================\n\n/**\n * Populate a `BaseColliderInfo3D` slot in place from raw component data.\n * Returns `true` if the slot was filled, `false` if the entity has no\n * collider (caller should skip it).\n *\n * If an entity has both AABB3D and sphere colliders, AABB3D wins and only\n * the AABB3D offset is applied.\n */\nexport function fillBaseColliderInfo3D<L extends string>(\n\tinfo: BaseColliderInfo3D<L>,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tz: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb3D: { width: number; height: number; depth: number; offsetX?: number; offsetY?: number; offsetZ?: number } | undefined,\n\tsphere: { radius: number; offsetX?: number; offsetY?: number; offsetZ?: number } | undefined,\n): boolean {\n\tinfo.entityId = entityId;\n\tinfo.layer = layer;\n\tinfo.collidesWith = collidesWith;\n\tinfo.layerBit = getLayerBit3D(layer);\n\tinfo.collidesWithMask = getCollidesWithMask3D(collidesWith);\n\n\tif (aabb3D) {\n\t\tinfo.x = x + (aabb3D.offsetX ?? 0);\n\t\tinfo.y = y + (aabb3D.offsetY ?? 0);\n\t\tinfo.z = z + (aabb3D.offsetZ ?? 0);\n\t\tinfo.shape = AABB3D_SHAPE;\n\t\tinfo.halfWidth = aabb3D.width / 2;\n\t\tinfo.halfHeight = aabb3D.height / 2;\n\t\tinfo.halfDepth = aabb3D.depth / 2;\n\t\tinfo.radius = 0;\n\t\treturn true;\n\t}\n\n\tif (sphere) {\n\t\tinfo.x = x + (sphere.offsetX ?? 0);\n\t\tinfo.y = y + (sphere.offsetY ?? 0);\n\t\tinfo.z = z + (sphere.offsetZ ?? 0);\n\t\tinfo.shape = SPHERE_SHAPE;\n\t\tinfo.halfWidth = 0;\n\t\tinfo.halfHeight = 0;\n\t\tinfo.halfDepth = 0;\n\t\tinfo.radius = sphere.radius;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex3D resource, returning undefined when absent.\n * Centralizes the cross-plugin typed lookup so individual plugins don't each\n * need to import SpatialIndex3D or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex3D(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex3D | undefined {\n\treturn tryGetResource<SpatialIndex3D>('spatialIndex3D');\n}\n\n// ==================== Narrowphase Tests ====================\n\n/**\n * Write an AABB3D-vs-AABB3D contact into `out`. Returns `true` if the\n * shapes overlap (out was filled), `false` otherwise.\n *\n * Resolves along the axis with minimum penetration depth.\n */\nexport function computeAABB3DvsAABB3D(\n\tax: number, ay: number, az: number, ahw: number, ahh: number, ahd: number,\n\tbx: number, by: number, bz: number, bhw: number, bhh: number, bhd: number,\n\tout: Contact3D,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst dz = bz - az;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\tconst overlapZ = (ahd + bhd) - Math.abs(dz);\n\n\tif (overlapX <= 0 || overlapY <= 0 || overlapZ <= 0) return false;\n\n\tif (overlapX <= overlapY && overlapX <= overlapZ) {\n\t\tout.normalX = dx >= 0 ? 1 : -1;\n\t\tout.normalY = 0;\n\t\tout.normalZ = 0;\n\t\tout.depth = overlapX;\n\t\treturn true;\n\t}\n\n\tif (overlapY <= overlapZ) {\n\t\tout.normalX = 0;\n\t\tout.normalY = dy >= 0 ? 1 : -1;\n\t\tout.normalZ = 0;\n\t\tout.depth = overlapY;\n\t\treturn true;\n\t}\n\n\tout.normalX = 0;\n\tout.normalY = 0;\n\tout.normalZ = dz >= 0 ? 1 : -1;\n\tout.depth = overlapZ;\n\treturn true;\n}\n\n/**\n * Write a sphere-vs-sphere contact into `out`. Returns `true` if the\n * spheres overlap.\n */\nexport function computeSphereVsSphere(\n\tax: number, ay: number, az: number, ar: number,\n\tbx: number, by: number, bz: number, br: number,\n\tout: Contact3D,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst dz = bz - az;\n\tconst distSq = dx * dx + dy * dy + dz * dz;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return false;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\tout.normalX = 1;\n\t\tout.normalY = 0;\n\t\tout.normalZ = 0;\n\t\tout.depth = radiusSum;\n\t\treturn true;\n\t}\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.normalZ = dz / dist;\n\tout.depth = radiusSum - dist;\n\treturn true;\n}\n\n/**\n * Write an AABB3D-vs-sphere contact into `out`. Returns `true` if the\n * shapes overlap.\n *\n * Uses closest-point-on-AABB to sphere center. When the sphere center\n * is inside the AABB, resolves along the axis with minimum push distance.\n */\nexport function computeAABB3DvsSphere(\n\taabbX: number, aabbY: number, aabbZ: number, ahw: number, ahh: number, ahd: number,\n\tsphereX: number, sphereY: number, sphereZ: number, radius: number,\n\tout: Contact3D,\n): boolean {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(sphereX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(sphereY, aabbY + ahh));\n\tconst closestZ = Math.max(aabbZ - ahd, Math.min(sphereZ, aabbZ + ahd));\n\n\tconst dx = sphereX - closestX;\n\tconst dy = sphereY - closestY;\n\tconst dz = sphereZ - closestZ;\n\tconst distSq = dx * dx + dy * dy + dz * dz;\n\n\tif (distSq >= radius * radius) return false;\n\n\t// Sphere center inside AABB — resolve along minimum push axis\n\tif (distSq === 0) {\n\t\tconst pushLeft = (sphereX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - sphereX);\n\t\tconst pushUp = (sphereY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - sphereY);\n\t\tconst pushFront = (sphereZ - (aabbZ - ahd));\n\t\tconst pushBack = ((aabbZ + ahd) - sphereZ);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown, pushFront, pushBack);\n\n\t\tif (minPush === pushRight) {\n\t\t\tout.normalX = 1; out.normalY = 0; out.normalZ = 0; out.depth = pushRight + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushLeft) {\n\t\t\tout.normalX = -1; out.normalY = 0; out.normalZ = 0; out.depth = pushLeft + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushDown) {\n\t\t\tout.normalX = 0; out.normalY = 1; out.normalZ = 0; out.depth = pushDown + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushUp) {\n\t\t\tout.normalX = 0; out.normalY = -1; out.normalZ = 0; out.depth = pushUp + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushBack) {\n\t\t\tout.normalX = 0; out.normalY = 0; out.normalZ = 1; out.depth = pushBack + radius;\n\t\t\treturn true;\n\t\t}\n\t\tout.normalX = 0; out.normalY = 0; out.normalZ = -1; out.depth = pushFront + radius;\n\t\treturn true;\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.normalZ = dz / dist;\n\tout.depth = radius - dist;\n\treturn true;\n}\n\n// ==================== Contact Dispatcher ====================\n\n/**\n * Dispatch to the correct narrowphase function for the given pair and\n * write the contact into `out`. Returns `true` if the pair overlaps.\n */\nexport function computeContact3D(a: BaseColliderInfo3D, b: BaseColliderInfo3D, out: Contact3D): boolean {\n\tif (a.shape === AABB3D_SHAPE && b.shape === AABB3D_SHAPE) {\n\t\treturn computeAABB3DvsAABB3D(\n\t\t\ta.x, a.y, a.z, a.halfWidth, a.halfHeight, a.halfDepth,\n\t\t\tb.x, b.y, b.z, b.halfWidth, b.halfHeight, b.halfDepth,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === SPHERE_SHAPE && b.shape === SPHERE_SHAPE) {\n\t\treturn computeSphereVsSphere(\n\t\t\ta.x, a.y, a.z, a.radius,\n\t\t\tb.x, b.y, b.z, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === AABB3D_SHAPE && b.shape === SPHERE_SHAPE) {\n\t\treturn computeAABB3DvsSphere(\n\t\t\ta.x, a.y, a.z, a.halfWidth, a.halfHeight, a.halfDepth,\n\t\t\tb.x, b.y, b.z, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\t// a is Sphere, b is AABB3D — compute as AABB3D-vs-Sphere, then flip normal\n\tif (!computeAABB3DvsSphere(\n\t\tb.x, b.y, b.z, b.halfWidth, b.halfHeight, b.halfDepth,\n\t\ta.x, a.y, a.z, a.radius,\n\t\tout,\n\t)) return false;\n\tout.normalX = -out.normalX;\n\tout.normalY = -out.normalY;\n\tout.normalZ = -out.normalZ;\n\treturn true;\n}\n\n// ==================== Collision Iteration ====================\n\n/** Module-level reusable array for broadphase candidates. */\nconst _broadphaseCandidates: number[] = [];\n\nlet _bruteForceWarned = false;\nconst BRUTE_FORCE_WARN_THRESHOLD = 50;\n\n/**\n * Generic 3D collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * `count` is the number of live entries at the front of `colliders`.\n * The array itself may be a grow-only pool — only indices `[0, count)`\n * are iterated, so trailing pool slots are ignored.\n *\n * `workingMap` is a caller-owned `Map<number, I>` used by the broadphase\n * path as an entityId → collider lookup. It is cleared and repopulated on\n * each call; callers should allocate it once and pass the same instance\n * every frame.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions3D<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tworkingMap: Map<number, I>,\n\tspatialIndex: SpatialIndex3D | undefined,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, count, workingMap, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, count, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tif (!_bruteForceWarned && count >= BRUTE_FORCE_WARN_THRESHOLD) {\n\t\t_bruteForceWarned = true;\n\t\tconsole.warn(\n\t\t\t`[ecspresso] 3D collision detection is using O(n²) brute force with ${count} colliders. ` +\n\t\t\t`For better performance, install createSpatialIndex3DPlugin() alongside your collision or physics3D plugin.`,\n\t\t);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tfor (let j = i + 1; j < count; j++) {\n\t\t\tconst b = colliders[j];\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact3D(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo3D, C>(\n\tcolliders: I[],\n\tcount: number,\n\tcolliderMap: Map<number, I>,\n\tspatialIndex: SpatialIndex3D,\n\tonContact: (a: I, b: I, contact: Contact3D, context: C) => void,\n\tcontext: C,\n): void {\n\tcolliderMap.clear();\n\tfor (let i = 0; i < count; i++) {\n\t\tconst c = colliders[i];\n\t\tif (!c) continue;\n\t\tcolliderMap.set(c.entityId, c);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tconst aHalfW = a.shape === AABB3D_SHAPE ? a.halfWidth : a.radius;\n\t\tconst aHalfH = a.shape === AABB3D_SHAPE ? a.halfHeight : a.radius;\n\t\tconst aHalfD = a.shape === AABB3D_SHAPE ? a.halfDepth : a.radius;\n\n\t\t_broadphaseCandidates.length = 0;\n\t\tspatialIndex.queryBoxInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH, a.z - aHalfD,\n\t\t\ta.x + aHalfW, a.y + aHalfH, a.z + aHalfD,\n\t\t\t_broadphaseCandidates,\n\t\t\ta.entityId,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tconst b = colliderMap.get(bId);\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact3D(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n"
10
10
  ],
11
- "mappings": "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",
12
- "debugId": "397322863DC03B6264756E2164756E21",
11
+ "mappings": "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",
12
+ "debugId": "D3C63757EDEB721A64756E2164756E21",
13
13
  "names": []
14
14
  }
@@ -1,4 +1,4 @@
1
- var i=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(J,Q)=>(typeof require<"u"?require:J)[Q]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{definePlugin as s}from"ecspresso";function A(z){let J=new Map,Q=new WeakMap,j=1;function $(K){let Z=J.get(K);if(Z!==void 0)return Z;if(j===0)throw Error(`[ecspresso] ${z} layer bitmask overflow: more than 32 distinct layers registered`);let V=j;return J.set(K,V),j<<=1,V}function O(K){let Z=Q.get(K);if(Z!==void 0)return Z;let V=0;for(let G=0;G<K.length;G++)V|=$(K[G]);return Q.set(K,V),V}return{getLayerBit:$,getCollidesWithMask:O}}var S={normalX:0,normalY:0,depth:0},E=0,w=1,v=A("Collision"),y=v.getLayerBit,u=v.getCollidesWithMask;function g(z,J,Q,j,$,O,K,Z){if(z.entityId=J,z.layer=$,z.collidesWith=O,z.layerBit=y($),z.collidesWithMask=u(O),K)return z.x=Q+(K.offsetX??0),z.y=j+(K.offsetY??0),z.shape=E,z.halfWidth=K.width/2,z.halfHeight=K.height/2,z.radius=0,!0;if(Z)return z.x=Q+(Z.offsetX??0),z.y=j+(Z.offsetY??0),z.shape=w,z.halfWidth=0,z.halfHeight=0,z.radius=Z.radius,!0;return!1}function b(z,J,Q,j,$,O,K,Z,V){let G=$-z,U=O-J,F=Q+K-Math.abs(G),k=j+Z-Math.abs(U);if(F<=0||k<=0)return!1;if(F<k)return V.normalX=G>=0?1:-1,V.normalY=0,V.depth=F,!0;return V.normalX=0,V.normalY=U>=0?1:-1,V.depth=k,!0}function d(z,J,Q,j,$,O,K){let Z=j-z,V=$-J,G=Z*Z+V*V,U=Q+O;if(G>=U*U)return!1;let F=Math.sqrt(G);if(F===0)return K.normalX=1,K.normalY=0,K.depth=U,!0;return K.normalX=Z/F,K.normalY=V/F,K.depth=U-F,!0}function B(z,J,Q,j,$,O,K,Z){let V=Math.max(z-Q,Math.min($,z+Q)),G=Math.max(J-j,Math.min(O,J+j)),U=$-V,F=O-G,k=U*U+F*F;if(k>=K*K)return!1;if(k===0){let q=$-(z-Q),D=z+Q-$,T=O-(J-j),L=J+j-O,P=Math.min(q,D,T,L);if(P===D)return Z.normalX=1,Z.normalY=0,Z.depth=D+K,!0;if(P===q)return Z.normalX=-1,Z.normalY=0,Z.depth=q+K,!0;if(P===L)return Z.normalX=0,Z.normalY=1,Z.depth=L+K,!0;return Z.normalX=0,Z.normalY=-1,Z.depth=T+K,!0}let N=Math.sqrt(k);return Z.normalX=U/N,Z.normalY=F/N,Z.depth=K-N,!0}function f(z,J,Q){if(z.shape===E&&J.shape===E)return b(z.x,z.y,z.halfWidth,z.halfHeight,J.x,J.y,J.halfWidth,J.halfHeight,Q);if(z.shape===w&&J.shape===w)return d(z.x,z.y,z.radius,J.x,J.y,J.radius,Q);if(z.shape===E&&J.shape===w)return B(z.x,z.y,z.halfWidth,z.halfHeight,J.x,J.y,J.radius,Q);if(!B(J.x,J.y,J.halfWidth,J.halfHeight,z.x,z.y,z.radius,Q))return!1;return Q.normalX=-Q.normalX,Q.normalY=-Q.normalY,!0}var C=[];function p(){return{arr:[],gen:[],current:0}}var m=!1,r=50;function h(z,J,Q,j,$,O){if(j)n(z,J,Q,j,$,O);else l(z,J,$,O)}function l(z,J,Q,j){if(!m&&J>=r)m=!0,console.warn(`[ecspresso] Collision detection is using O(n²) brute force with ${J} colliders. For better performance, install createSpatialIndexPlugin() alongside your collision or physics2D plugin.`);for(let $=0;$<J;$++){let O=z[$];if(!O)continue;for(let K=$+1;K<J;K++){let Z=z[K];if(!Z)continue;if((O.collidesWithMask&Z.layerBit|Z.collidesWithMask&O.layerBit)===0)continue;if(!f(O,Z,S))continue;Q(O,Z,S,j)}}}function n(z,J,Q,j,$,O){let{arr:K,gen:Z}=Q,V=++Q.current;for(let G=0;G<J;G++){let U=z[G];if(!U)continue;let F=U.entityId;K[F]=U,Z[F]=V}for(let G=0;G<J;G++){let U=z[G];if(!U)continue;let F=U.shape===E?U.halfWidth:U.radius,k=U.shape===E?U.halfHeight:U.radius;C.length=0,j.queryRectInto(U.x-F,U.y-k,U.x+F,U.y+k,C,U.entityId);for(let N of C){if(Z[N]!==V)continue;let q=K[N];if(!q)continue;if((U.collidesWithMask&q.layerBit|q.collidesWithMask&U.layerBit)===0)continue;if(!f(U,q,S))continue;$(U,q,S,O)}}}function Qz(z,J){return{rigidBody:{type:z,mass:z==="static"?1/0:J?.mass??1,drag:J?.drag??0,restitution:J?.restitution??0,friction:J?.friction??0,gravityScale:J?.gravityScale??1},force:{x:0,y:0}}}function Zz(z,J){return{force:{x:z,y:J}}}function $z(z,J,Q,j){let $=z.getComponent(J,"force");if(!$)return;$.x+=Q,$.y+=j}function Kz(z,J,Q,j){let $=z.getComponent(J,"velocity"),O=z.getComponent(J,"rigidBody");if(!$||!O)return;if(O.mass===1/0||O.mass===0)return;$.x+=Q/O.mass,$.y+=j/O.mass}function Oz(z,J,Q,j){let $=z.getComponent(J,"velocity");if(!$)return;$.x=Q,$.y=j}var Y={entityA:0,entityB:0,normalX:0,normalY:0,depth:0};function c(z,J,Q,j){let $=z.rigidBody.type==="dynamic"&&z.rigidBody.mass>0&&z.rigidBody.mass!==1/0?1/z.rigidBody.mass:0,O=J.rigidBody.type==="dynamic"&&J.rigidBody.mass>0&&J.rigidBody.mass!==1/0?1/J.rigidBody.mass:0,K=$+O;if(K>0){let Z=Q.depth/K;if($>0){let F=j.getComponent(z.entityId,"localTransform");if(!F)return;let k=Z*$;F.x-=k*Q.normalX,F.y-=k*Q.normalY,z.x=F.x,z.y=F.y,j.markChanged(z.entityId,"localTransform")}if(O>0){let F=j.getComponent(J.entityId,"localTransform");if(!F)return;let k=Z*O;F.x+=k*Q.normalX,F.y+=k*Q.normalY,J.x=F.x,J.y=F.y,j.markChanged(J.entityId,"localTransform")}let V=J.velocity.x-z.velocity.x,G=J.velocity.y-z.velocity.y,U=V*Q.normalX+G*Q.normalY;if(U<0){let k=-(1+Math.min(z.rigidBody.restitution,J.rigidBody.restitution))*U/K;z.velocity.x-=k*$*Q.normalX,z.velocity.y-=k*$*Q.normalY,J.velocity.x+=k*O*Q.normalX,J.velocity.y+=k*O*Q.normalY;let N=V-U*Q.normalX,q=G-U*Q.normalY,D=Math.sqrt(N*N+q*q);if(D>0.000001){let T=N/D,L=q/D,X=Math.sqrt(z.rigidBody.friction*J.rigidBody.friction)*Math.abs(k),R=Math.min(D/K,X);z.velocity.x+=R*$*T,z.velocity.y+=R*$*L,J.velocity.x-=R*O*T,J.velocity.y-=R*O*L}}j.markChanged(z.entityId,"velocity"),j.markChanged(J.entityId,"velocity")}Y.entityA=z.entityId,Y.entityB=J.entityId,Y.normalX=Q.normalX,Y.normalY=Q.normalY,Y.depth=Q.depth,j.eventBus.publish("physicsCollision",Y)}function jz(z){let{gravity:J={x:0,y:0},systemGroup:Q="physics2D",collisionSystemGroup:j,integrationPriority:$=1000,collisionPriority:O=900,phase:K="fixedUpdate"}=z??{};return s("physics2D").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().requires().install((Z)=>{Z.registerRequired("rigidBody","velocity",()=>({x:0,y:0})),Z.registerRequired("rigidBody","force",()=>({x:0,y:0})),Z.addResource("physicsConfig",{gravity:{x:J.x,y:J.y}}),Z.addSystem("physics2D-integration").setPriority($).inPhase(K).inGroup(Q).addQuery("bodies",{with:["localTransform","velocity","rigidBody","force"]}).setProcess(({queries:N,dt:q,ecs:D})=>{let{gravity:T}=D.getResource("physicsConfig"),L=T.x,P=T.y;for(let X of N.bodies){let{localTransform:R,velocity:H,rigidBody:M,force:W}=X.components;if(M.type==="static")continue;if(M.type==="dynamic"){let x=M.gravityScale*q;H.x+=L*x,H.y+=P*x;let I=M.mass;if(I>0&&I!==1/0){let _=q/I;H.x+=W.x*_,H.y+=W.y*_}if(M.drag>0){let _=Math.max(0,1-M.drag*q);H.x*=_,H.y*=_}}R.x+=H.x*q,R.y+=H.y*q,W.x=0,W.y=0,D.markChanged(X.id,"localTransform")}});let V=Z.addSystem("physics2D-collision").setPriority(O).inPhase(K).inGroup(Q);if(j)V.inGroup(j);let G=[],U=p(),F,k=!1;V.addQuery("collidables",{with:["localTransform","rigidBody","velocity","collisionLayer"]}).setProcess(({queries:N,ecs:q})=>{let D=0;for(let T of N.collidables){let{localTransform:L,rigidBody:P,velocity:X,collisionLayer:R}=T.components,H=q.getComponent(T.id,"aabbCollider"),M=H?void 0:q.getComponent(T.id,"circleCollider");if(!H&&!M)continue;let W=G[D];if(!W)W={entityId:T.id,x:L.x,y:L.y,layer:R.layer,collidesWith:R.collidesWith,layerBit:0,collidesWithMask:0,shape:E,halfWidth:0,halfHeight:0,radius:0,rigidBody:P,velocity:X},G[D]=W;else W.rigidBody=P,W.velocity=X;if(!g(W,T.id,L.x,L.y,R.layer,R.collidesWith,H,M))continue;D++}if(!k)F=q.tryGetResource("spatialIndex"),k=!0;h(G,D,U,F,c,q)})})}export{Oz as setVelocity,Qz as createRigidBody,jz as createPhysics2DPlugin,Zz as createForce,Kz as applyImpulse,$z as applyForce};
1
+ var e=((z)=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(z,{get:(J,K)=>(typeof require<"u"?require:J)[K]}):z)(function(z){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+z+'" is not supported')});import{definePlugin as c}from"ecspresso";function B(z){let J=new Map,K=new WeakMap,j=1;function Z($){let Q=J.get($);if(Q!==void 0)return Q;if(j===0)throw Error(`[ecspresso] ${z} layer bitmask overflow: more than 32 distinct layers registered`);let V=j;return J.set($,V),j<<=1,V}function O($){let Q=K.get($);if(Q!==void 0)return Q;let V=0;for(let G=0;G<$.length;G++)V|=Z($[G]);return K.set($,V),V}return{getLayerBit:Z,getCollidesWithMask:O}}var I={normalX:0,normalY:0,depth:0},X=0,S=1,f=B("Collision"),b=f.getLayerBit,u=f.getCollidesWithMask;function C(z,J,K,j,Z,O,$,Q){if(z.entityId=J,z.layer=Z,z.collidesWith=O,z.layerBit=b(Z),z.collidesWithMask=u(O),$)return z.x=K+($.offsetX??0),z.y=j+($.offsetY??0),z.shape=X,z.halfWidth=$.width/2,z.halfHeight=$.height/2,z.radius=0,!0;if(Q)return z.x=K+(Q.offsetX??0),z.y=j+(Q.offsetY??0),z.shape=S,z.halfWidth=0,z.halfHeight=0,z.radius=Q.radius,!0;return!1}function d(z,J,K,j,Z,O,$,Q,V){let G=Z-z,U=O-J,F=K+$-Math.abs(G),D=j+Q-Math.abs(U);if(F<=0||D<=0)return!1;if(F<D)return V.normalX=G>=0?1:-1,V.normalY=0,V.depth=F,!0;return V.normalX=0,V.normalY=U>=0?1:-1,V.depth=D,!0}function r(z,J,K,j,Z,O,$){let Q=j-z,V=Z-J,G=Q*Q+V*V,U=K+O;if(G>=U*U)return!1;let F=Math.sqrt(G);if(F===0)return $.normalX=1,$.normalY=0,$.depth=U,!0;return $.normalX=Q/F,$.normalY=V/F,$.depth=U-F,!0}function g(z,J,K,j,Z,O,$,Q){let V=Math.max(z-K,Math.min(Z,z+K)),G=Math.max(J-j,Math.min(O,J+j)),U=Z-V,F=O-G,D=U*U+F*F;if(D>=$*$)return!1;if(D===0){let k=Z-(z-K),q=z+K-Z,R=O-(J-j),N=J+j-O,H=Math.min(k,q,R,N);if(H===q)return Q.normalX=1,Q.normalY=0,Q.depth=q+$,!0;if(H===k)return Q.normalX=-1,Q.normalY=0,Q.depth=k+$,!0;if(H===N)return Q.normalX=0,Q.normalY=1,Q.depth=N+$,!0;return Q.normalX=0,Q.normalY=-1,Q.depth=R+$,!0}let W=Math.sqrt(D);return Q.normalX=U/W,Q.normalY=F/W,Q.depth=$-W,!0}function p(z,J,K){if(z.shape===X&&J.shape===X)return d(z.x,z.y,z.halfWidth,z.halfHeight,J.x,J.y,J.halfWidth,J.halfHeight,K);if(z.shape===S&&J.shape===S)return r(z.x,z.y,z.radius,J.x,J.y,J.radius,K);if(z.shape===X&&J.shape===S)return g(z.x,z.y,z.halfWidth,z.halfHeight,J.x,J.y,J.radius,K);if(!g(J.x,J.y,J.halfWidth,J.halfHeight,z.x,z.y,z.radius,K))return!1;return K.normalX=-K.normalX,K.normalY=-K.normalY,!0}var x=[];function h(){return{arr:[],gen:[],current:0}}var v=!1,n=50;function y(z,J,K,j,Z,O){if(j)l(z,J,K,j,Z,O);else s(z,J,Z,O)}function s(z,J,K,j){if(!v&&J>=n)v=!0,console.warn(`[ecspresso] Collision detection is using O(n²) brute force with ${J} colliders. For better performance, install createSpatialIndexPlugin() alongside your collision or physics2D plugin.`);for(let Z=0;Z<J;Z++){let O=z[Z];if(!O)continue;for(let $=Z+1;$<J;$++){let Q=z[$];if(!Q)continue;if((O.collidesWithMask&Q.layerBit|Q.collidesWithMask&O.layerBit)===0)continue;if(!p(O,Q,I))continue;K(O,Q,I,j)}}}function l(z,J,K,j,Z,O){let{arr:$,gen:Q}=K,V=++K.current;for(let G=0;G<J;G++){let U=z[G];if(!U)continue;let F=U.entityId;$[F]=U,Q[F]=V}for(let G=0;G<J;G++){let U=z[G];if(!U)continue;let F=U.shape===X?U.halfWidth:U.radius,D=U.shape===X?U.halfHeight:U.radius;x.length=0,j.queryRectInto(U.x-F,U.y-D,U.x+F,U.y+D,x,U.entityId);for(let W of x){if(Q[W]!==V)continue;let k=$[W];if(!k)continue;if((U.collidesWithMask&k.layerBit|k.collidesWithMask&U.layerBit)===0)continue;if(!p(U,k,I))continue;Z(U,k,I,O)}}}function Qz(z,J){return{rigidBody:{type:z,mass:z==="static"?1/0:J?.mass??1,drag:J?.drag??0,restitution:J?.restitution??0,friction:J?.friction??0,gravityScale:J?.gravityScale??1},force:{x:0,y:0}}}function Zz(z,J){return{force:{x:z,y:J}}}function $z(z,J,K,j){let Z=z.getComponent(J,"force");if(!Z)return;Z.x+=K,Z.y+=j}function Oz(z,J,K,j){let Z=z.getComponent(J,"velocity"),O=z.getComponent(J,"rigidBody");if(!Z||!O)return;if(O.mass===1/0||O.mass===0)return;Z.x+=K/O.mass,Z.y+=j/O.mass}function jz(z,J,K,j){let Z=z.getComponent(J,"velocity");if(!Z)return;Z.x=K,Z.y=j}var _={entityA:0,entityB:0,normalX:0,normalY:0,depth:0};function i(z,J,K,j){let Z=z.rigidBody.type==="dynamic"&&z.rigidBody.mass>0&&z.rigidBody.mass!==1/0?1/z.rigidBody.mass:0,O=J.rigidBody.type==="dynamic"&&J.rigidBody.mass>0&&J.rigidBody.mass!==1/0?1/J.rigidBody.mass:0,$=Z+O;if($>0){let Q=K.depth/$;if(Z>0){let F=j.getComponent(z.entityId,"localTransform");if(!F)return;let D=Q*Z;F.x-=D*K.normalX,F.y-=D*K.normalY,z.x=F.x,z.y=F.y,j.markChanged(z.entityId,"localTransform")}if(O>0){let F=j.getComponent(J.entityId,"localTransform");if(!F)return;let D=Q*O;F.x+=D*K.normalX,F.y+=D*K.normalY,J.x=F.x,J.y=F.y,j.markChanged(J.entityId,"localTransform")}let V=J.velocity.x-z.velocity.x,G=J.velocity.y-z.velocity.y,U=V*K.normalX+G*K.normalY;if(U<0){let D=-(1+Math.min(z.rigidBody.restitution,J.rigidBody.restitution))*U/$;z.velocity.x-=D*Z*K.normalX,z.velocity.y-=D*Z*K.normalY,J.velocity.x+=D*O*K.normalX,J.velocity.y+=D*O*K.normalY;let W=V-U*K.normalX,k=G-U*K.normalY,q=Math.sqrt(W*W+k*k);if(q>0.000001){let R=W/q,N=k/q,E=Math.sqrt(z.rigidBody.friction*J.rigidBody.friction)*Math.abs(D),T=Math.min(q/$,E);z.velocity.x+=T*Z*R,z.velocity.y+=T*Z*N,J.velocity.x-=T*O*R,J.velocity.y-=T*O*N}}j.markChanged(z.entityId,"velocity"),j.markChanged(J.entityId,"velocity")}_.entityA=z.entityId,_.entityB=J.entityId,_.normalX=K.normalX,_.normalY=K.normalY,_.depth=K.depth,j.eventBus.publish("physicsCollision",_)}function Uz(z){let{gravity:J={x:0,y:0},systemGroup:K="physics2D",collisionSystemGroup:j,integrationPriority:Z=1000,collisionPriority:O=900,phase:$="fixedUpdate"}=z??{};return c("physics2D").withComponentTypes().withEventTypes().withResourceTypes().withLabels().withGroups().requires().install((Q)=>{Q.registerRequired("rigidBody","velocity",()=>({x:0,y:0})),Q.registerRequired("rigidBody","force",()=>({x:0,y:0})),Q.addResource("physicsConfig",{gravity:{x:J.x,y:J.y}}),Q.addSystem("physics2D-integration").setPriority(Z).inPhase($).inGroup(K).addQuery("bodies",{with:["localTransform","velocity","rigidBody","force"]}).setProcess(({queries:k,dt:q,ecs:R})=>{let{gravity:N}=R.getResource("physicsConfig"),H=N.x,E=N.y;for(let T of k.bodies){let{localTransform:P,velocity:M,rigidBody:L,force:Y}=T.components;if(L.type==="static")continue;if(L.type==="dynamic"){let m=L.gravityScale*q;M.x+=H*m,M.y+=E*m;let A=L.mass;if(A>0&&A!==1/0){let w=q/A;M.x+=Y.x*w,M.y+=Y.y*w}if(L.drag>0){let w=Math.max(0,1-L.drag*q);M.x*=w,M.y*=w}}P.x+=M.x*q,P.y+=M.y*q,Y.x=0,Y.y=0,R.markChanged(T.id,"localTransform")}});let V=Q.addSystem("physics2D-collision").setPriority(O).inPhase($).inGroup(K);if(j)V.inGroup(j);let G=[],U=h(),F,D=!1,W=(k,q,R,N,H,E)=>{let T=G[k];if(!T)T={entityId:q,x:0,y:0,layer:R,collidesWith:N,layerBit:0,collidesWithMask:0,shape:X,halfWidth:0,halfHeight:0,radius:0,rigidBody:H,velocity:E},G[k]=T;else T.rigidBody=H,T.velocity=E;return T};V.addQuery("aabbOnly",{with:["localTransform","rigidBody","velocity","collisionLayer","aabbCollider"],without:["circleCollider"]}).addQuery("circleOnly",{with:["localTransform","rigidBody","velocity","collisionLayer","circleCollider"],without:["aabbCollider"]}).addQuery("both",{with:["localTransform","rigidBody","velocity","collisionLayer","aabbCollider","circleCollider"]}).setProcess(({queries:k,ecs:q})=>{let R=0;for(let N of k.aabbOnly){let{localTransform:H,rigidBody:E,velocity:T,collisionLayer:P,aabbCollider:M}=N.components,L=W(R,N.id,P.layer,P.collidesWith,E,T);if(!C(L,N.id,H.x,H.y,P.layer,P.collidesWith,M,void 0))continue;R++}for(let N of k.circleOnly){let{localTransform:H,rigidBody:E,velocity:T,collisionLayer:P,circleCollider:M}=N.components,L=W(R,N.id,P.layer,P.collidesWith,E,T);if(!C(L,N.id,H.x,H.y,P.layer,P.collidesWith,void 0,M))continue;R++}for(let N of k.both){let{localTransform:H,rigidBody:E,velocity:T,collisionLayer:P,aabbCollider:M,circleCollider:L}=N.components,Y=W(R,N.id,P.layer,P.collidesWith,E,T);if(!C(Y,N.id,H.x,H.y,P.layer,P.collidesWith,M,L))continue;R++}if(!D)F=q.tryGetResource("spatialIndex"),D=!0;y(G,R,U,F,i,q)})})}export{jz as setVelocity,Qz as createRigidBody,Uz as createPhysics2DPlugin,Zz as createForce,Oz as applyImpulse,$z as applyForce};
2
2
 
3
- //# debugId=07F3E3C2C1BC182564756E2164756E21
3
+ //# debugId=C7B6730AA14E017664756E2164756E21
4
4
  //# sourceMappingURL=physics2D.js.map
@@ -2,11 +2,11 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/plugins/physics/physics2D.ts", "../src/utils/layer-bit-registry.ts", "../src/utils/narrowphase.ts"],
4
4
  "sourcesContent": [
5
- "/**\n * Physics 2D Plugin for ECSpresso\n *\n * Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler\n * integration, and impulse-based collision response with friction.\n *\n * Reuses collider types from the collision plugin for shape definitions.\n * Has its own collision detection in fixedUpdate for physics response;\n * the existing collision plugin can still run in postUpdate for game logic events.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes, TransformWorldConfig } from '../spatial/transform';\nimport type { CollisionComponentTypes, LayerFactories } from './collision';\nimport type { Vector2D } from 'ecspresso';\nimport { fillBaseColliderInfo, detectCollisions, createBroadphaseScratch, AABB_SHAPE, type Contact, type BaseColliderInfo } from '../../utils/narrowphase';\nimport type { SpatialIndex } from '../../utils/spatial-hash';\n\n// ==================== Component Types ====================\n\n/**\n * Rigid body types for physics simulation.\n * - 'dynamic': Fully simulated (gravity, forces, collisions)\n * - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions)\n * - 'static': Immovable (ignores gravity, forces, and velocity)\n */\nexport type BodyType = 'dynamic' | 'kinematic' | 'static';\n\n/**\n * Rigid body component controlling physics behavior.\n */\nexport interface RigidBody {\n\ttype: BodyType;\n\t/** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */\n\tmass: number;\n\t/** Linear velocity damping coefficient (units/sec, 0 = none) */\n\tdrag: number;\n\t/** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */\n\trestitution: number;\n\t/** Surface friction coefficient 0–1 */\n\tfriction: number;\n\t/** Per-entity gravity multiplier (0 = no gravity) */\n\tgravityScale: number;\n}\n\n/**\n * Component types directly provided by the physics plugin.\n */\nexport interface Physics2DOwnComponentTypes {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n\tforce: Vector2D;\n}\n\n/**\n * Full component types available when using the physics plugin\n * (own components + transform + collision dependencies).\n * Convenience alias for consumer code.\n */\nexport interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L>, Physics2DOwnComponentTypes {}\n\n// ==================== Resource Types ====================\n\n/**\n * Physics configuration resource.\n */\nexport interface Physics2DConfig {\n\tgravity: Vector2D;\n}\n\nexport interface Physics2DResourceTypes {\n\tphysicsConfig: Physics2DConfig;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event emitted for each physics collision pair.\n *\n * Normal components are flattened (`normalX`/`normalY`) rather than nested\n * in a `Vector2D` to avoid a per-event allocation in the physics hot path.\n */\nexport interface Physics2DCollisionEvent {\n\tentityA: number;\n\tentityB: number;\n\t/** Unit normal X, pointing from A toward B */\n\tnormalX: number;\n\t/** Unit normal Y, pointing from A toward B */\n\tnormalY: number;\n\t/** Penetration depth (positive) */\n\tdepth: number;\n}\n\nexport interface Physics2DEventTypes {\n\tphysicsCollision: Physics2DCollisionEvent;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface Physics2DPluginOptions<G extends string = 'physics2D', CG extends string = never> {\n\t/** World gravity vector (default: {x: 0, y: 0}) */\n\tgravity?: Vector2D;\n\t/** System group name (default: 'physics2D') */\n\tsystemGroup?: G;\n\t/** Additional group for the collision system only (default: none).\n\t * When set, the collision system belongs to both `systemGroup` and this group,\n\t * allowing independent enable/disable of collision detection. */\n\tcollisionSystemGroup?: CG;\n\t/** Priority for integration system (default: 1000) */\n\tintegrationPriority?: number;\n\t/** Priority for collision system (default: 900) */\n\tcollisionPriority?: number;\n\t/** Execution phase (default: 'fixedUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\nexport interface RigidBodyOptions {\n\tmass?: number;\n\tdrag?: number;\n\trestitution?: number;\n\tfriction?: number;\n\tgravityScale?: number;\n}\n\n/**\n * Create a rigid body + force component pair.\n * Static bodies automatically get mass=Infinity.\n */\nexport function createRigidBody(\n\ttype: BodyType,\n\toptions?: RigidBodyOptions,\n): { rigidBody: RigidBody; force: Vector2D } {\n\treturn {\n\t\trigidBody: {\n\t\t\ttype,\n\t\t\tmass: type === 'static' ? Infinity : (options?.mass ?? 1),\n\t\t\tdrag: options?.drag ?? 0,\n\t\t\trestitution: options?.restitution ?? 0,\n\t\t\tfriction: options?.friction ?? 0,\n\t\t\tgravityScale: options?.gravityScale ?? 1,\n\t\t},\n\t\tforce: { x: 0, y: 0 },\n\t};\n}\n\n/**\n * Create a force component with initial values.\n */\nexport function createForce(x: number, y: number): { force: Vector2D } {\n\treturn { force: { x, y } };\n}\n\n/**\n * Accumulate a force onto an entity's force component.\n */\nexport function applyForce(\n\tecs: { getComponent(id: number, name: 'force'): Vector2D | undefined },\n\tentityId: number,\n\tfx: number,\n\tfy: number,\n): void {\n\tconst force = ecs.getComponent(entityId, 'force');\n\tif (!force) return;\n\tforce.x += fx;\n\tforce.y += fy;\n}\n\n/**\n * Apply an instantaneous impulse: velocity += impulse / mass.\n */\nexport function applyImpulse(\n\tecs: {\n\t\tgetComponent(id: number, name: 'velocity'): Vector2D | undefined;\n\t\tgetComponent(id: number, name: 'rigidBody'): RigidBody | undefined;\n\t},\n\tentityId: number,\n\tix: number,\n\tiy: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity');\n\tconst rigidBody = ecs.getComponent(entityId, 'rigidBody');\n\tif (!velocity || !rigidBody) return;\n\tif (rigidBody.mass === Infinity || rigidBody.mass === 0) return;\n\tvelocity.x += ix / rigidBody.mass;\n\tvelocity.y += iy / rigidBody.mass;\n}\n\n/**\n * Directly set an entity's velocity.\n */\nexport function setVelocity(\n\tecs: { getComponent(id: number, name: 'velocity'): Vector2D | undefined },\n\tentityId: number,\n\tvx: number,\n\tvy: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity');\n\tif (!velocity) return;\n\tvelocity.x = vx;\n\tvelocity.y = vy;\n}\n\n// ==================== Internal: Collider Info ====================\n\ninterface Physics2DColliderInfo<L extends string = string> extends BaseColliderInfo<L> {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n}\n\n// ==================== Collision Response ====================\n\n/**\n * Module-level reusable physics collision event. Subscribers must consume\n * synchronously — same contract as the shared narrowphase Contact.\n */\nconst _physicsCollisionEvent: Physics2DCollisionEvent = {\n\tentityA: 0, entityB: 0, normalX: 0, normalY: 0, depth: 0,\n};\n\ninterface PhysicsEcsLike {\n\tgetComponent(id: number, name: 'localTransform'): { x: number; y: number } | undefined;\n\teventBus: { publish(event: 'physicsCollision', data: Physics2DCollisionEvent): void };\n\tmarkChanged(entityId: number, componentName: 'localTransform' | 'velocity'): void;\n}\n\n/**\n * Resolve a physics collision pair: position correction, impulse response, event.\n */\nfunction resolvePhysicsContact(\n\ta: Physics2DColliderInfo,\n\tb: Physics2DColliderInfo,\n\tcontact: Contact,\n\tecs: PhysicsEcsLike,\n): void {\n\tconst invMassA = (a.rigidBody.type === 'dynamic' && a.rigidBody.mass > 0 && a.rigidBody.mass !== Infinity)\n\t\t? 1 / a.rigidBody.mass\n\t\t: 0;\n\tconst invMassB = (b.rigidBody.type === 'dynamic' && b.rigidBody.mass > 0 && b.rigidBody.mass !== Infinity)\n\t\t? 1 / b.rigidBody.mass\n\t\t: 0;\n\tconst totalInvMass = invMassA + invMassB;\n\n\t// Position correction\n\tif (totalInvMass > 0) {\n\t\tconst correctionScale = contact.depth / totalInvMass;\n\n\t\tif (invMassA > 0) {\n\t\t\tconst ltA = ecs.getComponent(a.entityId, 'localTransform');\n\t\t\tif (!ltA) return;\n\t\t\tconst corrA = correctionScale * invMassA;\n\t\t\tltA.x -= corrA * contact.normalX;\n\t\t\tltA.y -= corrA * contact.normalY;\n\t\t\t// Sync cached position so subsequent pairs in this frame use corrected values\n\t\t\ta.x = ltA.x;\n\t\t\ta.y = ltA.y;\n\t\t\tecs.markChanged(a.entityId, 'localTransform');\n\t\t}\n\n\t\tif (invMassB > 0) {\n\t\t\tconst ltB = ecs.getComponent(b.entityId, 'localTransform');\n\t\t\tif (!ltB) return;\n\t\t\tconst corrB = correctionScale * invMassB;\n\t\t\tltB.x += corrB * contact.normalX;\n\t\t\tltB.y += corrB * contact.normalY;\n\t\t\tb.x = ltB.x;\n\t\t\tb.y = ltB.y;\n\t\t\tecs.markChanged(b.entityId, 'localTransform');\n\t\t}\n\n\t\t// Velocity response (impulse-based)\n\t\tconst relVelX = b.velocity.x - a.velocity.x;\n\t\tconst relVelY = b.velocity.y - a.velocity.y;\n\t\tconst velAlongNormal = relVelX * contact.normalX + relVelY * contact.normalY;\n\n\t\tif (velAlongNormal < 0) {\n\t\t\tconst restitution = Math.min(a.rigidBody.restitution, b.rigidBody.restitution);\n\t\t\tconst normalImpulse = -(1 + restitution) * velAlongNormal / totalInvMass;\n\n\t\t\ta.velocity.x -= normalImpulse * invMassA * contact.normalX;\n\t\t\ta.velocity.y -= normalImpulse * invMassA * contact.normalY;\n\t\t\tb.velocity.x += normalImpulse * invMassB * contact.normalX;\n\t\t\tb.velocity.y += normalImpulse * invMassB * contact.normalY;\n\n\t\t\t// Friction (tangential impulse)\n\t\t\tconst tangentX = relVelX - velAlongNormal * contact.normalX;\n\t\t\tconst tangentY = relVelY - velAlongNormal * contact.normalY;\n\t\t\tconst tangentSpeed = Math.sqrt(tangentX * tangentX + tangentY * tangentY);\n\n\t\t\tif (tangentSpeed > 1e-6) {\n\t\t\t\tconst tangentNX = tangentX / tangentSpeed;\n\t\t\t\tconst tangentNY = tangentY / tangentSpeed;\n\t\t\t\tconst friction = Math.sqrt(a.rigidBody.friction * b.rigidBody.friction);\n\t\t\t\tconst maxFrictionImpulse = friction * Math.abs(normalImpulse);\n\t\t\t\tconst tangentImpulse = Math.min(tangentSpeed / totalInvMass, maxFrictionImpulse);\n\n\t\t\t\ta.velocity.x += tangentImpulse * invMassA * tangentNX;\n\t\t\t\ta.velocity.y += tangentImpulse * invMassA * tangentNY;\n\t\t\t\tb.velocity.x -= tangentImpulse * invMassB * tangentNX;\n\t\t\t\tb.velocity.y -= tangentImpulse * invMassB * tangentNY;\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(a.entityId, 'velocity');\n\t\tecs.markChanged(b.entityId, 'velocity');\n\t}\n\n\t_physicsCollisionEvent.entityA = a.entityId;\n\t_physicsCollisionEvent.entityB = b.entityId;\n\t_physicsCollisionEvent.normalX = contact.normalX;\n\t_physicsCollisionEvent.normalY = contact.normalY;\n\t_physicsCollisionEvent.depth = contact.depth;\n\tecs.eventBus.publish('physicsCollision', _physicsCollisionEvent);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 2D physics plugin for ECSpresso.\n *\n * Provides:\n * - Semi-implicit Euler integration (gravity, forces, drag → velocity → position)\n * - Impulse-based collision response with restitution and friction\n * - physicsCollision events with contact normal and depth\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createPhysics2DPlugin({ gravity: { x: 0, y: 980 } }))\n * .withFixedTimestep(1/60)\n * .build();\n *\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }),\n * velocity: { x: 0, y: 0 },\n * ...createAABBCollider(32, 32),\n * ...createCollisionLayer('player', ['ground']),\n * });\n * ```\n */\n\ntype Physics2DProvides<L extends string = never> = Physics2DOwnComponentTypes & CollisionComponentTypes<L>;\n\nexport function createPhysics2DPlugin<L extends string = never, G extends string = 'physics2D', CG extends string = never>(\n\toptions?: Physics2DPluginOptions<G, CG> & { layers?: LayerFactories<Record<L, readonly string[]>> },\n) {\n\tconst {\n\t\tgravity = { x: 0, y: 0 },\n\t\tsystemGroup = 'physics2D',\n\t\tcollisionSystemGroup,\n\t\tintegrationPriority = 1000,\n\t\tcollisionPriority = 900,\n\t\tphase = 'fixedUpdate',\n\t} = options ?? {};\n\n\treturn definePlugin('physics2D')\n\t\t.withComponentTypes<Physics2DProvides<L>>()\n\t\t.withEventTypes<Physics2DEventTypes>()\n\t\t.withResourceTypes<Physics2DResourceTypes>()\n\t\t.withLabels<'physics2D-integration' | 'physics2D-collision'>()\n\t\t.withGroups<G | CG>()\n\t\t.requires<TransformWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// rigidBody requires velocity and force — auto-add with zero defaults\n\t\t\tworld.registerRequired('rigidBody', 'velocity', () => ({ x: 0, y: 0 }));\n\t\t\tworld.registerRequired('rigidBody', 'force', () => ({ x: 0, y: 0 }));\n\n\t\t\tworld.addResource('physicsConfig', { gravity: { x: gravity.x, y: gravity.y } });\n\n\t\t\t// ==================== Integration System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('physics2D-integration')\n\t\t\t\t.setPriority(integrationPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('bodies', {\n\t\t\t\t\twith: ['localTransform', 'velocity', 'rigidBody', 'force'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tconst { gravity: g } = ecs.getResource('physicsConfig');\n\t\t\t\t\tconst gx = g.x;\n\t\t\t\t\tconst gy = g.y;\n\n\t\t\t\t\t// TODO(perf): no early-out for \"sleeping\" dynamic bodies — a packed\n\t\t\t\t\t// pile of resting entities still runs gravity/drag/force-clear/\n\t\t\t\t\t// markChanged every step. A sleep flag on RigidBody that latches\n\t\t\t\t\t// after N frames of near-zero velocity (and clears on impulse or\n\t\t\t\t\t// applied force) would let most of a stabilized scene skip the\n\t\t\t\t\t// full per-entity body of this loop. Keep in sync with physics3D.\n\t\t\t\t\tfor (const entity of queries.bodies) {\n\t\t\t\t\t\tconst { localTransform, velocity, rigidBody, force } = entity.components;\n\n\t\t\t\t\t\t// Static bodies: skip entirely\n\t\t\t\t\t\tif (rigidBody.type === 'static') continue;\n\n\t\t\t\t\t\t// Dynamic bodies: apply gravity, forces, drag\n\t\t\t\t\t\tif (rigidBody.type === 'dynamic') {\n\t\t\t\t\t\t\t// 1. Gravity\n\t\t\t\t\t\t\tconst gsdt = rigidBody.gravityScale * dt;\n\t\t\t\t\t\t\tvelocity.x += gx * gsdt;\n\t\t\t\t\t\t\tvelocity.y += gy * gsdt;\n\n\t\t\t\t\t\t\t// 2. Forces (F = ma → a = F/m)\n\t\t\t\t\t\t\tconst mass = rigidBody.mass;\n\t\t\t\t\t\t\tif (mass > 0 && mass !== Infinity) {\n\t\t\t\t\t\t\t\tconst invMassDt = dt / mass;\n\t\t\t\t\t\t\t\tvelocity.x += force.x * invMassDt;\n\t\t\t\t\t\t\t\tvelocity.y += force.y * invMassDt;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 3. Drag\n\t\t\t\t\t\t\tif (rigidBody.drag > 0) {\n\t\t\t\t\t\t\t\tconst damping = Math.max(0, 1 - rigidBody.drag * dt);\n\t\t\t\t\t\t\t\tvelocity.x *= damping;\n\t\t\t\t\t\t\t\tvelocity.y *= damping;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Both dynamic and kinematic: integrate position\n\t\t\t\t\t\tlocalTransform.x += velocity.x * dt;\n\t\t\t\t\t\tlocalTransform.y += velocity.y * dt;\n\n\t\t\t\t\t\t// Clear accumulated forces\n\t\t\t\t\t\tforce.x = 0;\n\t\t\t\t\t\tforce.y = 0;\n\n\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Collision System ====================\n\n\t\t\tconst collisionSystem = world\n\t\t\t\t.addSystem('physics2D-collision')\n\t\t\t\t.setPriority(collisionPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup);\n\n\t\t\tif (collisionSystemGroup) {\n\t\t\t\tcollisionSystem.inGroup(collisionSystemGroup);\n\t\t\t}\n\n\t\t\t// Grow-only pool of Physics2DColliderInfo slots reused across frames.\n\t\t\t// Steady-state: zero allocations per frame once the pool is warm.\n\t\t\tconst colliderPool: Physics2DColliderInfo<L>[] = [];\n\t\t\tconst broadphaseScratch = createBroadphaseScratch<Physics2DColliderInfo<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tcollisionSystem\n\t\t\t\t.addQuery('collidables', {\n\t\t\t\t\twith: ['localTransform', 'rigidBody', 'velocity', 'collisionLayer'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\t// TODO(perf): collider shape is discovered via two ecs.getComponent\n\t\t\t\t\t// calls per entity per frame because the query can't express\n\t\t\t\t\t// \"aabbCollider OR circleCollider\". Splitting into two queries\n\t\t\t\t\t// (aabb-bearing, circle-bearing) would eliminate these lookups at\n\t\t\t\t\t// the cost of two pool-fill passes. Keep in sync with physics3D.\n\t\t\t\t\tfor (const entity of queries.collidables) {\n\t\t\t\t\t\tconst { localTransform, rigidBody, velocity, collisionLayer } = entity.components;\n\t\t\t\t\t\tconst aabb = ecs.getComponent(entity.id, 'aabbCollider');\n\t\t\t\t\t\tconst circle = aabb ? undefined : ecs.getComponent(entity.id, 'circleCollider');\n\t\t\t\t\t\tif (!aabb && !circle) continue;\n\n\t\t\t\t\t\tlet slot = colliderPool[count];\n\t\t\t\t\t\tif (!slot) {\n\t\t\t\t\t\t\tslot = {\n\t\t\t\t\t\t\t\tentityId: entity.id,\n\t\t\t\t\t\t\t\tx: localTransform.x,\n\t\t\t\t\t\t\t\ty: localTransform.y,\n\t\t\t\t\t\t\t\tlayer: collisionLayer.layer,\n\t\t\t\t\t\t\t\tcollidesWith: collisionLayer.collidesWith,\n\t\t\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\t\t\tshape: AABB_SHAPE,\n\t\t\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t\t\t\trigidBody,\n\t\t\t\t\t\t\t\tvelocity,\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\tcolliderPool[count] = slot;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tslot.rigidBody = rigidBody;\n\t\t\t\t\t\t\tslot.velocity = velocity;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, localTransform.x, localTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabb, circle,\n\t\t\t\t\t\t)) continue;\n\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex>('spatialIndex');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions(colliderPool, count, broadphaseScratch, cachedSI, resolvePhysicsContact, ecs);\n\t\t\t\t});\n\t\t});\n}\n",
5
+ "/**\n * Physics 2D Plugin for ECSpresso\n *\n * Provides ECS-native arcade physics: gravity, forces, drag, semi-implicit Euler\n * integration, and impulse-based collision response with friction.\n *\n * Reuses collider types from the collision plugin for shape definitions.\n * Has its own collision detection in fixedUpdate for physics response;\n * the existing collision plugin can still run in postUpdate for game logic events.\n */\n\nimport { definePlugin } from 'ecspresso';\nimport type { SystemPhase } from 'ecspresso';\nimport type { TransformComponentTypes, TransformWorldConfig } from '../spatial/transform';\nimport type { CollisionComponentTypes, LayerFactories } from './collision';\nimport type { Vector2D } from 'ecspresso';\nimport { fillBaseColliderInfo, detectCollisions, createBroadphaseScratch, AABB_SHAPE, type Contact, type BaseColliderInfo } from '../../utils/narrowphase';\nimport type { SpatialIndex } from '../../utils/spatial-hash';\n\n// ==================== Component Types ====================\n\n/**\n * Rigid body types for physics simulation.\n * - 'dynamic': Fully simulated (gravity, forces, collisions)\n * - 'kinematic': Moves via velocity only (ignores gravity/forces, immovable in collisions)\n * - 'static': Immovable (ignores gravity, forces, and velocity)\n */\nexport type BodyType = 'dynamic' | 'kinematic' | 'static';\n\n/**\n * Rigid body component controlling physics behavior.\n */\nexport interface RigidBody {\n\ttype: BodyType;\n\t/** Mass in arbitrary units. Affects force→acceleration. Infinity = immovable. */\n\tmass: number;\n\t/** Linear velocity damping coefficient (units/sec, 0 = none) */\n\tdrag: number;\n\t/** Bounciness 0–1 (0 = no bounce, 1 = perfectly elastic) */\n\trestitution: number;\n\t/** Surface friction coefficient 0–1 */\n\tfriction: number;\n\t/** Per-entity gravity multiplier (0 = no gravity) */\n\tgravityScale: number;\n}\n\n/**\n * Component types directly provided by the physics plugin.\n */\nexport interface Physics2DOwnComponentTypes {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n\tforce: Vector2D;\n}\n\n/**\n * Full component types available when using the physics plugin\n * (own components + transform + collision dependencies).\n * Convenience alias for consumer code.\n */\nexport interface Physics2DComponentTypes<L extends string = never> extends TransformComponentTypes, CollisionComponentTypes<L>, Physics2DOwnComponentTypes {}\n\n// ==================== Resource Types ====================\n\n/**\n * Physics configuration resource.\n */\nexport interface Physics2DConfig {\n\tgravity: Vector2D;\n}\n\nexport interface Physics2DResourceTypes {\n\tphysicsConfig: Physics2DConfig;\n}\n\n// ==================== Event Types ====================\n\n/**\n * Event emitted for each physics collision pair.\n *\n * Normal components are flattened (`normalX`/`normalY`) rather than nested\n * in a `Vector2D` to avoid a per-event allocation in the physics hot path.\n */\nexport interface Physics2DCollisionEvent {\n\tentityA: number;\n\tentityB: number;\n\t/** Unit normal X, pointing from A toward B */\n\tnormalX: number;\n\t/** Unit normal Y, pointing from A toward B */\n\tnormalY: number;\n\t/** Penetration depth (positive) */\n\tdepth: number;\n}\n\nexport interface Physics2DEventTypes {\n\tphysicsCollision: Physics2DCollisionEvent;\n}\n\n// ==================== Plugin Options ====================\n\nexport interface Physics2DPluginOptions<G extends string = 'physics2D', CG extends string = never> {\n\t/** World gravity vector (default: {x: 0, y: 0}) */\n\tgravity?: Vector2D;\n\t/** System group name (default: 'physics2D') */\n\tsystemGroup?: G;\n\t/** Additional group for the collision system only (default: none).\n\t * When set, the collision system belongs to both `systemGroup` and this group,\n\t * allowing independent enable/disable of collision detection. */\n\tcollisionSystemGroup?: CG;\n\t/** Priority for integration system (default: 1000) */\n\tintegrationPriority?: number;\n\t/** Priority for collision system (default: 900) */\n\tcollisionPriority?: number;\n\t/** Execution phase (default: 'fixedUpdate') */\n\tphase?: SystemPhase;\n}\n\n// ==================== Helper Functions ====================\n\nexport interface RigidBodyOptions {\n\tmass?: number;\n\tdrag?: number;\n\trestitution?: number;\n\tfriction?: number;\n\tgravityScale?: number;\n}\n\n/**\n * Create a rigid body + force component pair.\n * Static bodies automatically get mass=Infinity.\n */\nexport function createRigidBody(\n\ttype: BodyType,\n\toptions?: RigidBodyOptions,\n): { rigidBody: RigidBody; force: Vector2D } {\n\treturn {\n\t\trigidBody: {\n\t\t\ttype,\n\t\t\tmass: type === 'static' ? Infinity : (options?.mass ?? 1),\n\t\t\tdrag: options?.drag ?? 0,\n\t\t\trestitution: options?.restitution ?? 0,\n\t\t\tfriction: options?.friction ?? 0,\n\t\t\tgravityScale: options?.gravityScale ?? 1,\n\t\t},\n\t\tforce: { x: 0, y: 0 },\n\t};\n}\n\n/**\n * Create a force component with initial values.\n */\nexport function createForce(x: number, y: number): { force: Vector2D } {\n\treturn { force: { x, y } };\n}\n\n/**\n * Accumulate a force onto an entity's force component.\n */\nexport function applyForce(\n\tecs: { getComponent(id: number, name: 'force'): Vector2D | undefined },\n\tentityId: number,\n\tfx: number,\n\tfy: number,\n): void {\n\tconst force = ecs.getComponent(entityId, 'force');\n\tif (!force) return;\n\tforce.x += fx;\n\tforce.y += fy;\n}\n\n/**\n * Apply an instantaneous impulse: velocity += impulse / mass.\n */\nexport function applyImpulse(\n\tecs: {\n\t\tgetComponent(id: number, name: 'velocity'): Vector2D | undefined;\n\t\tgetComponent(id: number, name: 'rigidBody'): RigidBody | undefined;\n\t},\n\tentityId: number,\n\tix: number,\n\tiy: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity');\n\tconst rigidBody = ecs.getComponent(entityId, 'rigidBody');\n\tif (!velocity || !rigidBody) return;\n\tif (rigidBody.mass === Infinity || rigidBody.mass === 0) return;\n\tvelocity.x += ix / rigidBody.mass;\n\tvelocity.y += iy / rigidBody.mass;\n}\n\n/**\n * Directly set an entity's velocity.\n */\nexport function setVelocity(\n\tecs: { getComponent(id: number, name: 'velocity'): Vector2D | undefined },\n\tentityId: number,\n\tvx: number,\n\tvy: number,\n): void {\n\tconst velocity = ecs.getComponent(entityId, 'velocity');\n\tif (!velocity) return;\n\tvelocity.x = vx;\n\tvelocity.y = vy;\n}\n\n// ==================== Internal: Collider Info ====================\n\ninterface Physics2DColliderInfo<L extends string = string> extends BaseColliderInfo<L> {\n\trigidBody: RigidBody;\n\tvelocity: Vector2D;\n}\n\n// ==================== Collision Response ====================\n\n/**\n * Module-level reusable physics collision event. Subscribers must consume\n * synchronously — same contract as the shared narrowphase Contact.\n */\nconst _physicsCollisionEvent: Physics2DCollisionEvent = {\n\tentityA: 0, entityB: 0, normalX: 0, normalY: 0, depth: 0,\n};\n\ninterface PhysicsEcsLike {\n\tgetComponent(id: number, name: 'localTransform'): { x: number; y: number } | undefined;\n\teventBus: { publish(event: 'physicsCollision', data: Physics2DCollisionEvent): void };\n\tmarkChanged(entityId: number, componentName: 'localTransform' | 'velocity'): void;\n}\n\n/**\n * Resolve a physics collision pair: position correction, impulse response, event.\n */\nfunction resolvePhysicsContact(\n\ta: Physics2DColliderInfo,\n\tb: Physics2DColliderInfo,\n\tcontact: Contact,\n\tecs: PhysicsEcsLike,\n): void {\n\tconst invMassA = (a.rigidBody.type === 'dynamic' && a.rigidBody.mass > 0 && a.rigidBody.mass !== Infinity)\n\t\t? 1 / a.rigidBody.mass\n\t\t: 0;\n\tconst invMassB = (b.rigidBody.type === 'dynamic' && b.rigidBody.mass > 0 && b.rigidBody.mass !== Infinity)\n\t\t? 1 / b.rigidBody.mass\n\t\t: 0;\n\tconst totalInvMass = invMassA + invMassB;\n\n\t// Position correction\n\tif (totalInvMass > 0) {\n\t\tconst correctionScale = contact.depth / totalInvMass;\n\n\t\tif (invMassA > 0) {\n\t\t\tconst ltA = ecs.getComponent(a.entityId, 'localTransform');\n\t\t\tif (!ltA) return;\n\t\t\tconst corrA = correctionScale * invMassA;\n\t\t\tltA.x -= corrA * contact.normalX;\n\t\t\tltA.y -= corrA * contact.normalY;\n\t\t\t// Sync cached position so subsequent pairs in this frame use corrected values\n\t\t\ta.x = ltA.x;\n\t\t\ta.y = ltA.y;\n\t\t\tecs.markChanged(a.entityId, 'localTransform');\n\t\t}\n\n\t\tif (invMassB > 0) {\n\t\t\tconst ltB = ecs.getComponent(b.entityId, 'localTransform');\n\t\t\tif (!ltB) return;\n\t\t\tconst corrB = correctionScale * invMassB;\n\t\t\tltB.x += corrB * contact.normalX;\n\t\t\tltB.y += corrB * contact.normalY;\n\t\t\tb.x = ltB.x;\n\t\t\tb.y = ltB.y;\n\t\t\tecs.markChanged(b.entityId, 'localTransform');\n\t\t}\n\n\t\t// Velocity response (impulse-based)\n\t\tconst relVelX = b.velocity.x - a.velocity.x;\n\t\tconst relVelY = b.velocity.y - a.velocity.y;\n\t\tconst velAlongNormal = relVelX * contact.normalX + relVelY * contact.normalY;\n\n\t\tif (velAlongNormal < 0) {\n\t\t\tconst restitution = Math.min(a.rigidBody.restitution, b.rigidBody.restitution);\n\t\t\tconst normalImpulse = -(1 + restitution) * velAlongNormal / totalInvMass;\n\n\t\t\ta.velocity.x -= normalImpulse * invMassA * contact.normalX;\n\t\t\ta.velocity.y -= normalImpulse * invMassA * contact.normalY;\n\t\t\tb.velocity.x += normalImpulse * invMassB * contact.normalX;\n\t\t\tb.velocity.y += normalImpulse * invMassB * contact.normalY;\n\n\t\t\t// Friction (tangential impulse)\n\t\t\tconst tangentX = relVelX - velAlongNormal * contact.normalX;\n\t\t\tconst tangentY = relVelY - velAlongNormal * contact.normalY;\n\t\t\tconst tangentSpeed = Math.sqrt(tangentX * tangentX + tangentY * tangentY);\n\n\t\t\tif (tangentSpeed > 1e-6) {\n\t\t\t\tconst tangentNX = tangentX / tangentSpeed;\n\t\t\t\tconst tangentNY = tangentY / tangentSpeed;\n\t\t\t\tconst friction = Math.sqrt(a.rigidBody.friction * b.rigidBody.friction);\n\t\t\t\tconst maxFrictionImpulse = friction * Math.abs(normalImpulse);\n\t\t\t\tconst tangentImpulse = Math.min(tangentSpeed / totalInvMass, maxFrictionImpulse);\n\n\t\t\t\ta.velocity.x += tangentImpulse * invMassA * tangentNX;\n\t\t\t\ta.velocity.y += tangentImpulse * invMassA * tangentNY;\n\t\t\t\tb.velocity.x -= tangentImpulse * invMassB * tangentNX;\n\t\t\t\tb.velocity.y -= tangentImpulse * invMassB * tangentNY;\n\t\t\t}\n\t\t}\n\n\t\tecs.markChanged(a.entityId, 'velocity');\n\t\tecs.markChanged(b.entityId, 'velocity');\n\t}\n\n\t_physicsCollisionEvent.entityA = a.entityId;\n\t_physicsCollisionEvent.entityB = b.entityId;\n\t_physicsCollisionEvent.normalX = contact.normalX;\n\t_physicsCollisionEvent.normalY = contact.normalY;\n\t_physicsCollisionEvent.depth = contact.depth;\n\tecs.eventBus.publish('physicsCollision', _physicsCollisionEvent);\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create a 2D physics plugin for ECSpresso.\n *\n * Provides:\n * - Semi-implicit Euler integration (gravity, forces, drag → velocity → position)\n * - Impulse-based collision response with restitution and friction\n * - physicsCollision events with contact normal and depth\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createTransformPlugin())\n * .withPlugin(createPhysics2DPlugin({ gravity: { x: 0, y: 980 } }))\n * .withFixedTimestep(1/60)\n * .build();\n *\n * ecs.spawn({\n * ...createTransform(100, 200),\n * ...createRigidBody('dynamic', { mass: 1, restitution: 0.5 }),\n * velocity: { x: 0, y: 0 },\n * ...createAABBCollider(32, 32),\n * ...createCollisionLayer('player', ['ground']),\n * });\n * ```\n */\n\ntype Physics2DProvides<L extends string = never> = Physics2DOwnComponentTypes & CollisionComponentTypes<L>;\n\nexport function createPhysics2DPlugin<L extends string = never, G extends string = 'physics2D', CG extends string = never>(\n\toptions?: Physics2DPluginOptions<G, CG> & { layers?: LayerFactories<Record<L, readonly string[]>> },\n) {\n\tconst {\n\t\tgravity = { x: 0, y: 0 },\n\t\tsystemGroup = 'physics2D',\n\t\tcollisionSystemGroup,\n\t\tintegrationPriority = 1000,\n\t\tcollisionPriority = 900,\n\t\tphase = 'fixedUpdate',\n\t} = options ?? {};\n\n\treturn definePlugin('physics2D')\n\t\t.withComponentTypes<Physics2DProvides<L>>()\n\t\t.withEventTypes<Physics2DEventTypes>()\n\t\t.withResourceTypes<Physics2DResourceTypes>()\n\t\t.withLabels<'physics2D-integration' | 'physics2D-collision'>()\n\t\t.withGroups<G | CG>()\n\t\t.requires<TransformWorldConfig>()\n\t\t.install((world) => {\n\t\t\t// rigidBody requires velocity and force — auto-add with zero defaults\n\t\t\tworld.registerRequired('rigidBody', 'velocity', () => ({ x: 0, y: 0 }));\n\t\t\tworld.registerRequired('rigidBody', 'force', () => ({ x: 0, y: 0 }));\n\n\t\t\tworld.addResource('physicsConfig', { gravity: { x: gravity.x, y: gravity.y } });\n\n\t\t\t// ==================== Integration System ====================\n\n\t\t\tworld\n\t\t\t\t.addSystem('physics2D-integration')\n\t\t\t\t.setPriority(integrationPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.addQuery('bodies', {\n\t\t\t\t\twith: ['localTransform', 'velocity', 'rigidBody', 'force'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, dt, ecs }) => {\n\t\t\t\t\tconst { gravity: g } = ecs.getResource('physicsConfig');\n\t\t\t\t\tconst gx = g.x;\n\t\t\t\t\tconst gy = g.y;\n\n\t\t\t\t\t// TODO(perf): no early-out for \"sleeping\" dynamic bodies — a packed\n\t\t\t\t\t// pile of resting entities still runs gravity/drag/force-clear/\n\t\t\t\t\t// markChanged every step. A sleep flag on RigidBody that latches\n\t\t\t\t\t// after N frames of near-zero velocity (and clears on impulse or\n\t\t\t\t\t// applied force) would let most of a stabilized scene skip the\n\t\t\t\t\t// full per-entity body of this loop. Keep in sync with physics3D.\n\t\t\t\t\tfor (const entity of queries.bodies) {\n\t\t\t\t\t\tconst { localTransform, velocity, rigidBody, force } = entity.components;\n\n\t\t\t\t\t\t// Static bodies: skip entirely\n\t\t\t\t\t\tif (rigidBody.type === 'static') continue;\n\n\t\t\t\t\t\t// Dynamic bodies: apply gravity, forces, drag\n\t\t\t\t\t\tif (rigidBody.type === 'dynamic') {\n\t\t\t\t\t\t\t// 1. Gravity\n\t\t\t\t\t\t\tconst gsdt = rigidBody.gravityScale * dt;\n\t\t\t\t\t\t\tvelocity.x += gx * gsdt;\n\t\t\t\t\t\t\tvelocity.y += gy * gsdt;\n\n\t\t\t\t\t\t\t// 2. Forces (F = ma → a = F/m)\n\t\t\t\t\t\t\tconst mass = rigidBody.mass;\n\t\t\t\t\t\t\tif (mass > 0 && mass !== Infinity) {\n\t\t\t\t\t\t\t\tconst invMassDt = dt / mass;\n\t\t\t\t\t\t\t\tvelocity.x += force.x * invMassDt;\n\t\t\t\t\t\t\t\tvelocity.y += force.y * invMassDt;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// 3. Drag\n\t\t\t\t\t\t\tif (rigidBody.drag > 0) {\n\t\t\t\t\t\t\t\tconst damping = Math.max(0, 1 - rigidBody.drag * dt);\n\t\t\t\t\t\t\t\tvelocity.x *= damping;\n\t\t\t\t\t\t\t\tvelocity.y *= damping;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Both dynamic and kinematic: integrate position\n\t\t\t\t\t\tlocalTransform.x += velocity.x * dt;\n\t\t\t\t\t\tlocalTransform.y += velocity.y * dt;\n\n\t\t\t\t\t\t// Clear accumulated forces\n\t\t\t\t\t\tforce.x = 0;\n\t\t\t\t\t\tforce.y = 0;\n\n\t\t\t\t\t\tecs.markChanged(entity.id, 'localTransform');\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// ==================== Collision System ====================\n\n\t\t\tconst collisionSystem = world\n\t\t\t\t.addSystem('physics2D-collision')\n\t\t\t\t.setPriority(collisionPriority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup);\n\n\t\t\tif (collisionSystemGroup) {\n\t\t\t\tcollisionSystem.inGroup(collisionSystemGroup);\n\t\t\t}\n\n\t\t\t// Grow-only pool of Physics2DColliderInfo slots reused across frames.\n\t\t\t// Steady-state: zero allocations per frame once the pool is warm.\n\t\t\tconst colliderPool: Physics2DColliderInfo<L>[] = [];\n\t\t\tconst broadphaseScratch = createBroadphaseScratch<Physics2DColliderInfo<L>>();\n\t\t\t// Cached spatial index reference (resolved once on first frame).\n\t\t\tlet cachedSI: SpatialIndex | undefined;\n\t\t\tlet siResolved = false;\n\n\t\t\tconst ensureSlot = (idx: number, entityId: number, layer: L, collidesWith: readonly L[], rigidBody: RigidBody, velocity: Vector2D): Physics2DColliderInfo<L> => {\n\t\t\t\tlet slot = colliderPool[idx];\n\t\t\t\tif (!slot) {\n\t\t\t\t\tslot = {\n\t\t\t\t\t\tentityId,\n\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\tlayer,\n\t\t\t\t\t\tcollidesWith,\n\t\t\t\t\t\tlayerBit: 0,\n\t\t\t\t\t\tcollidesWithMask: 0,\n\t\t\t\t\t\tshape: AABB_SHAPE,\n\t\t\t\t\t\thalfWidth: 0,\n\t\t\t\t\t\thalfHeight: 0,\n\t\t\t\t\t\tradius: 0,\n\t\t\t\t\t\trigidBody,\n\t\t\t\t\t\tvelocity,\n\t\t\t\t\t};\n\t\t\t\t\tcolliderPool[idx] = slot;\n\t\t\t\t} else {\n\t\t\t\t\tslot.rigidBody = rigidBody;\n\t\t\t\t\tslot.velocity = velocity;\n\t\t\t\t}\n\t\t\t\treturn slot;\n\t\t\t};\n\n\t\t\tcollisionSystem\n\t\t\t\t.addQuery('aabbOnly', {\n\t\t\t\t\twith: ['localTransform', 'rigidBody', 'velocity', 'collisionLayer', 'aabbCollider'],\n\t\t\t\t\twithout: ['circleCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('circleOnly', {\n\t\t\t\t\twith: ['localTransform', 'rigidBody', 'velocity', 'collisionLayer', 'circleCollider'],\n\t\t\t\t\twithout: ['aabbCollider'],\n\t\t\t\t})\n\t\t\t\t.addQuery('both', {\n\t\t\t\t\twith: ['localTransform', 'rigidBody', 'velocity', 'collisionLayer', 'aabbCollider', 'circleCollider'],\n\t\t\t\t})\n\t\t\t\t.setProcess(({ queries, ecs }) => {\n\t\t\t\t\tlet count = 0;\n\n\t\t\t\t\tfor (const entity of queries.aabbOnly) {\n\t\t\t\t\t\tconst { localTransform, rigidBody, velocity, collisionLayer, aabbCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith, rigidBody, velocity);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, localTransform.x, localTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabbCollider, undefined,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (const entity of queries.circleOnly) {\n\t\t\t\t\t\tconst { localTransform, rigidBody, velocity, collisionLayer, circleCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith, rigidBody, velocity);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, localTransform.x, localTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\tundefined, circleCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\t// fillBaseColliderInfo picks aabb when both present — preserves prior precedence.\n\t\t\t\t\tfor (const entity of queries.both) {\n\t\t\t\t\t\tconst { localTransform, rigidBody, velocity, collisionLayer, aabbCollider, circleCollider } = entity.components;\n\t\t\t\t\t\tconst slot = ensureSlot(count, entity.id, collisionLayer.layer, collisionLayer.collidesWith, rigidBody, velocity);\n\t\t\t\t\t\tif (!fillBaseColliderInfo(\n\t\t\t\t\t\t\tslot,\n\t\t\t\t\t\t\tentity.id, localTransform.x, localTransform.y,\n\t\t\t\t\t\t\tcollisionLayer.layer, collisionLayer.collidesWith,\n\t\t\t\t\t\t\taabbCollider, circleCollider,\n\t\t\t\t\t\t)) continue;\n\t\t\t\t\t\tcount++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!siResolved) {\n\t\t\t\t\t\tcachedSI = ecs.tryGetResource<SpatialIndex>('spatialIndex');\n\t\t\t\t\t\tsiResolved = true;\n\t\t\t\t\t}\n\t\t\t\t\tdetectCollisions(colliderPool, count, broadphaseScratch, cachedSI, resolvePhysicsContact, ecs);\n\t\t\t\t});\n\t\t});\n}\n",
6
6
  "/**\n * Lazy monotonic registry mapping layer name → unique bit. Lets pair\n * filtering use a single `(a.collidesWithMask & b.layerBit)` check\n * instead of `Array.includes` on every collision pair.\n *\n * One registry per dimension (2D and 3D) — user-defined layer namespaces\n * are independent, so bits should not be shared across systems.\n *\n * Maximum 32 layers per registry (one per bit in a 32-bit signed int).\n * Crossing the limit throws on the next `getLayerBit` call.\n */\nexport interface LayerBitRegistry {\n\tgetLayerBit(layer: string): number;\n\t/** OR of `getLayerBit` for every entry. Cached by array reference. */\n\tgetCollidesWithMask(collidesWith: readonly string[]): number;\n}\n\nexport function createLayerBitRegistry(label: string): LayerBitRegistry {\n\tconst layerBits = new Map<string, number>();\n\tconst maskCache = new WeakMap<readonly string[], number>();\n\tlet nextBit = 1;\n\n\tfunction getLayerBit(layer: string): number {\n\t\tconst existing = layerBits.get(layer);\n\t\tif (existing !== undefined) return existing;\n\t\tif (nextBit === 0) {\n\t\t\tthrow new Error(\n\t\t\t\t`[ecspresso] ${label} layer bitmask overflow: more than 32 distinct layers registered`,\n\t\t\t);\n\t\t}\n\t\tconst bit = nextBit;\n\t\tlayerBits.set(layer, bit);\n\t\t// `<<= 1` rolls 1<<31 to 0, which is detected on the next call.\n\t\tnextBit <<= 1;\n\t\treturn bit;\n\t}\n\n\tfunction getCollidesWithMask(collidesWith: readonly string[]): number {\n\t\tconst cached = maskCache.get(collidesWith);\n\t\tif (cached !== undefined) return cached;\n\t\tlet mask = 0;\n\t\tfor (let i = 0; i < collidesWith.length; i++) {\n\t\t\tmask |= getLayerBit(collidesWith[i]!);\n\t\t}\n\t\tmaskCache.set(collidesWith, mask);\n\t\treturn mask;\n\t}\n\n\treturn { getLayerBit, getCollidesWithMask };\n}\n",
7
7
  "/**\n * Shared Narrowphase Module\n *\n * Provides contact-computing narrowphase tests and a generic collision\n * iteration pipeline used by both the collision plugin (event-only) and\n * the physics2D plugin (impulse response).\n */\n\nimport type { SpatialIndex } from './spatial-hash';\nimport { createLayerBitRegistry } from './layer-bit-registry';\n\n// ==================== Contact ====================\n\n/**\n * Contact result from a narrowphase test. Normal points from A toward B.\n *\n * Narrowphase functions use this as an out-parameter: the caller owns the\n * struct, the function writes fields in place and returns `true` on hit.\n * The `onContact` callback in `detectCollisions` receives a shared module-\n * level instance — **subscribers must consume it synchronously and must not\n * retain the reference across frames**.\n */\nexport interface Contact {\n\tnormalX: number;\n\tnormalY: number;\n\t/** Penetration depth (positive = overlapping) */\n\tdepth: number;\n}\n\n/**\n * Module-level reusable Contact passed down from `detectCollisions` into\n * narrowphase tests and forwarded to the `onContact` callback. Reused across\n * every pair in every frame — zero allocation in the narrowphase hot path.\n */\nconst _sharedContact: Contact = { normalX: 0, normalY: 0, depth: 0 };\n\n// ==================== BaseColliderInfo ====================\n\n/** Collider shape discriminator for the flattened BaseColliderInfo layout. */\nexport const AABB_SHAPE = 0;\nexport const CIRCLE_SHAPE = 1;\nexport type ColliderShape = typeof AABB_SHAPE | typeof CIRCLE_SHAPE;\n\n/**\n * Minimum collider data shared by collision and physics bundles.\n *\n * Flat layout (no nested `aabb` / `circle` sub-objects): the `shape`\n * discriminator tells you whether to read `halfWidth`/`halfHeight`\n * (AABB) or `radius` (Circle). Unused fields are set to 0.\n *\n * This shape is pool-friendly — all fields are assigned in place each\n * frame without allocating nested objects.\n */\nexport interface BaseColliderInfo<L extends string = string> {\n\tentityId: number;\n\tx: number;\n\ty: number;\n\tlayer: L;\n\tcollidesWith: readonly L[];\n\t/**\n\t * Bit assigned to `layer` from the lazy layer registry. Populated by\n\t * `fillBaseColliderInfo`. Used together with `collidesWithMask` to\n\t * replace per-pair `Array.includes` layer checks with a single AND.\n\t */\n\tlayerBit: number;\n\t/** OR of `getLayerBit` for every entry in `collidesWith`. */\n\tcollidesWithMask: number;\n\tshape: ColliderShape;\n\thalfWidth: number;\n\thalfHeight: number;\n\tradius: number;\n}\n\n// ==================== Layer Bit Registry ====================\n\nconst _layerRegistry = createLayerBitRegistry('Collision');\nexport const getLayerBit = _layerRegistry.getLayerBit;\nexport const getCollidesWithMask = _layerRegistry.getCollidesWithMask;\n\n// ==================== Collider Construction ====================\n\n/**\n * Populate a `BaseColliderInfo` slot in place from raw component data.\n * Returns `true` if the slot was filled, `false` if the entity has no\n * collider (caller should skip it).\n *\n * If an entity has both AABB and circle colliders, AABB wins and only\n * the AABB offset is applied. This matches the dispatch precedence in\n * `computeContact`; the previous implementation stacked both offsets,\n * which was a bug.\n */\nexport function fillBaseColliderInfo<L extends string>(\n\tinfo: BaseColliderInfo<L>,\n\tentityId: number,\n\tx: number,\n\ty: number,\n\tlayer: L,\n\tcollidesWith: readonly L[],\n\taabb: { width: number; height: number; offsetX?: number; offsetY?: number } | undefined,\n\tcircle: { radius: number; offsetX?: number; offsetY?: number } | undefined,\n): boolean {\n\tinfo.entityId = entityId;\n\tinfo.layer = layer;\n\tinfo.collidesWith = collidesWith;\n\tinfo.layerBit = getLayerBit(layer);\n\tinfo.collidesWithMask = getCollidesWithMask(collidesWith);\n\n\tif (aabb) {\n\t\tinfo.x = x + (aabb.offsetX ?? 0);\n\t\tinfo.y = y + (aabb.offsetY ?? 0);\n\t\tinfo.shape = AABB_SHAPE;\n\t\tinfo.halfWidth = aabb.width / 2;\n\t\tinfo.halfHeight = aabb.height / 2;\n\t\tinfo.radius = 0;\n\t\treturn true;\n\t}\n\n\tif (circle) {\n\t\tinfo.x = x + (circle.offsetX ?? 0);\n\t\tinfo.y = y + (circle.offsetY ?? 0);\n\t\tinfo.shape = CIRCLE_SHAPE;\n\t\tinfo.halfWidth = 0;\n\t\tinfo.halfHeight = 0;\n\t\tinfo.radius = circle.radius;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n// ==================== Spatial Index Lookup ====================\n\n/**\n * Retrieve the optional spatialIndex resource, returning undefined when absent.\n * Centralizes the cross-plugin typed lookup so individual plugins don't each\n * need to import SpatialIndex or repeat the tryGetResource pattern.\n */\nexport function tryGetSpatialIndex(\n\ttryGetResource: <T>(key: string) => T | undefined,\n): SpatialIndex | undefined {\n\treturn tryGetResource<SpatialIndex>('spatialIndex');\n}\n\n// ==================== Narrowphase Tests ====================\n\n/**\n * Write an AABB-AABB contact into `out`. Returns `true` if the shapes\n * overlap (out was filled), `false` otherwise.\n */\nexport function computeAABBvsAABB(\n\tax: number, ay: number, ahw: number, ahh: number,\n\tbx: number, by: number, bhw: number, bhh: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst overlapX = (ahw + bhw) - Math.abs(dx);\n\tconst overlapY = (ahh + bhh) - Math.abs(dy);\n\n\tif (overlapX <= 0 || overlapY <= 0) return false;\n\n\tif (overlapX < overlapY) {\n\t\tout.normalX = dx >= 0 ? 1 : -1;\n\t\tout.normalY = 0;\n\t\tout.depth = overlapX;\n\t\treturn true;\n\t}\n\tout.normalX = 0;\n\tout.normalY = dy >= 0 ? 1 : -1;\n\tout.depth = overlapY;\n\treturn true;\n}\n\nexport function computeCircleVsCircle(\n\tax: number, ay: number, ar: number,\n\tbx: number, by: number, br: number,\n\tout: Contact,\n): boolean {\n\tconst dx = bx - ax;\n\tconst dy = by - ay;\n\tconst distSq = dx * dx + dy * dy;\n\tconst radiusSum = ar + br;\n\n\tif (distSq >= radiusSum * radiusSum) return false;\n\n\tconst dist = Math.sqrt(distSq);\n\tif (dist === 0) {\n\t\tout.normalX = 1;\n\t\tout.normalY = 0;\n\t\tout.depth = radiusSum;\n\t\treturn true;\n\t}\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radiusSum - dist;\n\treturn true;\n}\n\nexport function computeAABBvsCircle(\n\taabbX: number, aabbY: number, ahw: number, ahh: number,\n\tcircleX: number, circleY: number, radius: number,\n\tout: Contact,\n): boolean {\n\tconst closestX = Math.max(aabbX - ahw, Math.min(circleX, aabbX + ahw));\n\tconst closestY = Math.max(aabbY - ahh, Math.min(circleY, aabbY + ahh));\n\n\tconst dx = circleX - closestX;\n\tconst dy = circleY - closestY;\n\tconst distSq = dx * dx + dy * dy;\n\n\tif (distSq >= radius * radius) return false;\n\n\t// Circle center inside AABB\n\tif (distSq === 0) {\n\t\tconst pushLeft = (circleX - (aabbX - ahw));\n\t\tconst pushRight = ((aabbX + ahw) - circleX);\n\t\tconst pushUp = (circleY - (aabbY - ahh));\n\t\tconst pushDown = ((aabbY + ahh) - circleY);\n\t\tconst minPush = Math.min(pushLeft, pushRight, pushUp, pushDown);\n\n\t\tif (minPush === pushRight) {\n\t\t\tout.normalX = 1; out.normalY = 0; out.depth = pushRight + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushLeft) {\n\t\t\tout.normalX = -1; out.normalY = 0; out.depth = pushLeft + radius;\n\t\t\treturn true;\n\t\t}\n\t\tif (minPush === pushDown) {\n\t\t\tout.normalX = 0; out.normalY = 1; out.depth = pushDown + radius;\n\t\t\treturn true;\n\t\t}\n\t\tout.normalX = 0; out.normalY = -1; out.depth = pushUp + radius;\n\t\treturn true;\n\t}\n\n\tconst dist = Math.sqrt(distSq);\n\tout.normalX = dx / dist;\n\tout.normalY = dy / dist;\n\tout.depth = radius - dist;\n\treturn true;\n}\n\n// ==================== Contact Dispatcher ====================\n\n/**\n * Dispatch to the correct narrowphase function for the given pair and\n * write the contact into `out`. Returns `true` if the pair overlaps.\n */\nexport function computeContact(a: BaseColliderInfo, b: BaseColliderInfo, out: Contact): boolean {\n\tif (a.shape === AABB_SHAPE && b.shape === AABB_SHAPE) {\n\t\treturn computeAABBvsAABB(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === CIRCLE_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeCircleVsCircle(\n\t\t\ta.x, a.y, a.radius,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\tif (a.shape === AABB_SHAPE && b.shape === CIRCLE_SHAPE) {\n\t\treturn computeAABBvsCircle(\n\t\t\ta.x, a.y, a.halfWidth, a.halfHeight,\n\t\t\tb.x, b.y, b.radius,\n\t\t\tout,\n\t\t);\n\t}\n\n\t// a is Circle, b is AABB — compute as AABB-vs-Circle, then flip normal in place\n\tif (!computeAABBvsCircle(\n\t\tb.x, b.y, b.halfWidth, b.halfHeight,\n\t\ta.x, a.y, a.radius,\n\t\tout,\n\t)) return false;\n\tout.normalX = -out.normalX;\n\tout.normalY = -out.normalY;\n\treturn true;\n}\n\n// ==================== Collision Iteration ====================\n\nconst _broadphaseCandidates: number[] = [];\n\n/**\n * Per-caller scratch for the broadphase entityId → collider lookup.\n *\n * Dense `arr` indexed by entityId, paired with a `gen` stamp array that marks\n * which slots are live this call. Bumping `current` invalidates all prior\n * entries without clearing — replaces the per-frame `Map.clear()` + N\n * `Map.set()` allocation churn that a `Map<number, I>` would incur.\n *\n * Owned per plugin instance (alongside its `colliderPool`), so concurrent\n * worlds don't share state and `I` stays fully typed without erasure.\n */\nexport interface BroadphaseScratch<I extends BaseColliderInfo> {\n\tarr: (I | undefined)[];\n\tgen: number[];\n\tcurrent: number;\n}\n\nexport function createBroadphaseScratch<I extends BaseColliderInfo>(): BroadphaseScratch<I> {\n\treturn { arr: [], gen: [], current: 0 };\n}\n\nlet _bruteForceWarned = false;\nconst BRUTE_FORCE_WARN_THRESHOLD = 50;\n\n/**\n * Generic collision detection pipeline: brute-force or broadphase,\n * with layer filtering and contact computation.\n *\n * `count` is the number of live entries at the front of `colliders`.\n * The array itself may be a grow-only pool — only indices `[0, count)`\n * are iterated, so trailing pool slots are ignored.\n *\n * `scratch` is a caller-owned `BroadphaseScratch<I>` used by the broadphase\n * path as an entityId → collider lookup. Allocate it once per plugin instance\n * and pass the same reference every call.\n *\n * Uses a context parameter forwarded to the callback to avoid\n * per-frame closure allocation.\n */\nexport function detectCollisions<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tscratch: BroadphaseScratch<I>,\n\tspatialIndex: SpatialIndex | undefined,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (spatialIndex) {\n\t\tbroadphaseDetect(colliders, count, scratch, spatialIndex, onContact, context);\n\t} else {\n\t\tbruteForceDetect(colliders, count, onContact, context);\n\t}\n}\n\nfunction bruteForceDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tif (!_bruteForceWarned && count >= BRUTE_FORCE_WARN_THRESHOLD) {\n\t\t_bruteForceWarned = true;\n\t\tconsole.warn(\n\t\t\t`[ecspresso] Collision detection is using O(n²) brute force with ${count} colliders. ` +\n\t\t\t`For better performance, install createSpatialIndexPlugin() alongside your collision or physics2D plugin.`,\n\t\t);\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tfor (let j = i + 1; j < count; j++) {\n\t\t\tconst b = colliders[j];\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n\nfunction broadphaseDetect<I extends BaseColliderInfo, C>(\n\tcolliders: I[],\n\tcount: number,\n\tscratch: BroadphaseScratch<I>,\n\tspatialIndex: SpatialIndex,\n\tonContact: (a: I, b: I, contact: Contact, context: C) => void,\n\tcontext: C,\n): void {\n\tconst arr = scratch.arr;\n\tconst stamps = scratch.gen;\n\tconst gen = ++scratch.current;\n\tfor (let i = 0; i < count; i++) {\n\t\tconst c = colliders[i];\n\t\tif (!c) continue;\n\t\tconst id = c.entityId;\n\t\tarr[id] = c;\n\t\tstamps[id] = gen;\n\t}\n\n\tfor (let i = 0; i < count; i++) {\n\t\tconst a = colliders[i];\n\t\tif (!a) continue;\n\n\t\tconst aHalfW = a.shape === AABB_SHAPE ? a.halfWidth : a.radius;\n\t\tconst aHalfH = a.shape === AABB_SHAPE ? a.halfHeight : a.radius;\n\n\t\t_broadphaseCandidates.length = 0;\n\t\tspatialIndex.queryRectInto(\n\t\t\ta.x - aHalfW, a.y - aHalfH,\n\t\t\ta.x + aHalfW, a.y + aHalfH,\n\t\t\t_broadphaseCandidates,\n\t\t\ta.entityId,\n\t\t);\n\n\t\tfor (const bId of _broadphaseCandidates) {\n\t\t\tif (stamps[bId] !== gen) continue;\n\t\t\tconst b = arr[bId];\n\t\t\tif (!b) continue;\n\n\t\t\tif (((a.collidesWithMask & b.layerBit) | (b.collidesWithMask & a.layerBit)) === 0) continue;\n\n\t\t\tif (!computeContact(a, b, _sharedContact)) continue;\n\n\t\t\tonContact(a, b, _sharedContact, context);\n\t\t}\n\t}\n}\n"
8
8
  ],
9
- "mappings": "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",
10
- "debugId": "07F3E3C2C1BC182564756E2164756E21",
9
+ "mappings": "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",
10
+ "debugId": "C7B6730AA14E017664756E2164756E21",
11
11
  "names": []
12
12
  }