@daneren2005/shared-memory-physics 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/components/transform-component.ts","../src/components/velocity-component.ts","../src/components/body-component.ts","../src/components/bounciness-component.ts","../src/components/interpolation-component.ts","../src/components/registry.ts","../src/math/shapes.ts","../src/systems/collision.ts","../src/systems/spatial-index.ts","../src/systems/bounce.ts","../src/systems/physics-update.ts","../src/systems/physics-system.ts","../src/systems/interpolation-update.ts","../src/systems/interpolation-system.ts"],"sourcesContent":["import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// Where an entity is, how big it is, and which way it faces: a position, its box, and the box's rotation.\nexport interface TransformComponent {\n\tindex: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n}\n\n// The defining half of a transform, from the entity template: the size that gives an entity a transform at all\n// (a config with no size does not load one). `width`/`height` are the unrotated box and required, since a\n// zero-area box never overlaps anything. `angle` is radians counter-clockwise from +x, the game's to set. A\n// round entity may give a `radius` instead, loaded as a width and height of its diameter; both at once throws.\nexport interface TransformConfig {\n\twidth: number\n\theight: number\n\tangle?: number\n\tradius?: number\n}\n// The live runtime half: the physics system moves the position every run, so it round-trips through `save`. Size\n// and facing come back from the template. `x`/`y` are the centre of the box, not a corner.\nexport interface TransformSerialization {\n\tx: number\n\ty: number\n}\n\n// Indexes into the backing Float32Array block, exported because the physics update reads the same offsets off\n// the raw block.\nexport const TRANSFORM_X_INDEX = 0;\nexport const TRANSFORM_Y_INDEX = 1;\nexport const TRANSFORM_WIDTH_INDEX = 2;\nexport const TRANSFORM_HEIGHT_INDEX = 3;\nexport const TRANSFORM_ANGLE_INDEX = 4;\nexport const TRANSFORM_SIZE = 5;\n\nexport const transformDefinition: ComponentDefinition<TransformComponent, Float32Array, TransformConfig, TransformSerialization> = {\n\ttype: Float32Array,\n\tsize: TRANSFORM_SIZE,\n\t// A size marks a config as having a transform. x/y are not listed: they come off a save, not the template.\n\tloadProperties: ['width', 'height', 'radius'],\n\tload(entity, memory, config) {\n\t\t// A radius is the same size said differently; turn it into width/height so nothing downstream cares. Both\n\t\t// at once cannot be honoured, so it throws rather than picking a winner.\n\t\tconst radius = config.radius;\n\t\tlet width = config.width;\n\t\tlet height = config.height;\n\t\tif(radius !== undefined) {\n\t\t\tif(width !== undefined || height !== undefined) {\n\t\t\t\tthrow new Error('A transform takes either a radius or a width and height, not both');\n\t\t\t}\n\n\t\t\twidth = height = radius * 2;\n\t\t}\n\n\t\tconst index = memory.create([config.x, config.y, width, height, config.angle ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget x() {\n\t\t\t\treturn block[TRANSFORM_X_INDEX];\n\t\t\t},\n\t\t\tset x(value: number) {\n\t\t\t\tblock[TRANSFORM_X_INDEX] = value;\n\t\t\t},\n\t\t\tget y() {\n\t\t\t\treturn block[TRANSFORM_Y_INDEX];\n\t\t\t},\n\t\t\tset y(value: number) {\n\t\t\t\tblock[TRANSFORM_Y_INDEX] = value;\n\t\t\t},\n\t\t\tget width() {\n\t\t\t\treturn block[TRANSFORM_WIDTH_INDEX];\n\t\t\t},\n\t\t\tset width(value: number) {\n\t\t\t\tblock[TRANSFORM_WIDTH_INDEX] = value;\n\t\t\t},\n\t\t\tget height() {\n\t\t\t\treturn block[TRANSFORM_HEIGHT_INDEX];\n\t\t\t},\n\t\t\tset height(value: number) {\n\t\t\t\tblock[TRANSFORM_HEIGHT_INDEX] = value;\n\t\t\t},\n\t\t\tget angle() {\n\t\t\t\treturn block[TRANSFORM_ANGLE_INDEX];\n\t\t\t},\n\t\t\tset angle(value: number) {\n\t\t\t\tblock[TRANSFORM_ANGLE_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\treturn {\n\t\t\tx: component.x,\n\t\t\ty: component.y,\n\t\t};\n\t},\n};\n\nexport default transformDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// How fast an entity moves, in world units per second per axis. An entity with a transform and a velocity is\n// moved by the physics system every run.\nexport interface VelocityComponent {\n\tindex: number\n\tvelocityX: number\n\tvelocityY: number\n}\n\n// Named per-axis, not `x`/`y`, because configs are flat and `x`/`y` belong to the transform. Either axis alone\n// gives an entity a velocity, so both are optional and a missing one starts at 0.\nexport interface VelocityConfig {\n\tvelocityX?: number\n\tvelocityY?: number\n}\n// Both spawn config and live state (a game's systems apply forces), so it round-trips through `save`.\nexport type VelocitySerialization = VelocityConfig;\n\n// Indexes into the backing Float32Array block, exported because the physics update reads the same offsets off\n// the raw block.\nexport const VELOCITY_X_INDEX = 0;\nexport const VELOCITY_Y_INDEX = 1;\nexport const VELOCITY_SIZE = 2;\n\nexport const velocityDefinition: ComponentDefinition<VelocityComponent, Float32Array, VelocityConfig, VelocitySerialization> = {\n\ttype: Float32Array,\n\tsize: VELOCITY_SIZE,\n\tloadProperties: ['velocityX', 'velocityY'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.velocityX ?? 0, config.velocityY ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget velocityX() {\n\t\t\t\treturn block[VELOCITY_X_INDEX];\n\t\t\t},\n\t\t\tset velocityX(value: number) {\n\t\t\t\tblock[VELOCITY_X_INDEX] = value;\n\t\t\t},\n\t\t\tget velocityY() {\n\t\t\t\treturn block[VELOCITY_Y_INDEX];\n\t\t\t},\n\t\t\tset velocityY(value: number) {\n\t\t\t\tblock[VELOCITY_Y_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\treturn {\n\t\t\tvelocityX: component.velocityX,\n\t\t\tvelocityY: component.velocityY,\n\t\t};\n\t},\n};\n\nexport default velocityDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// What an entity collides as and what it collides with. A body is loaded for anything with a size, so the way\n// to opt out is `collideCategory: 0`, not leaving the component off. Categories are the game's to name; the\n// library only reads them as bits.\nexport interface BodyComponent {\n\tindex: number\n\tshape: number\n\tcollideCategory: number\n\tcollideMask: number\n\t// A sensor takes part in detection - a mover overlapping it still gets its onCollision - but stops nothing:\n\t// nothing is swept short against it or bounces off it. Only about the response; it obeys canCollide unchanged.\n\tsensor: boolean\n}\n\n// The shapes a body can be, all held in the same transform (position, width, height, angle):\n// SHAPE_RECTANGLE - a width x height box turned to `angle`\n// SHAPE_CIRCLE - a circle of diameter `width`, unaffected by `angle`\n// SHAPE_CAPSULE - `width` end to end and `height` thick, lying along the way it faces\n// See src/math/shapes.ts for the geometry.\nexport const SHAPE_RECTANGLE = 1;\nexport const SHAPE_CIRCLE = 2;\nexport const SHAPE_CAPSULE = 3;\n\nconst KNOWN_SHAPES = [SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE];\n\n// Default category: a single bit, so a world that sets no categories behaves as before they existed.\nexport const DEFAULT_COLLIDE_CATEGORY = 1;\n// Default mask: every bit, so a default body meets anything whose own mask allows it. Stored in a Uint32Array,\n// so 0xFFFFFFFF ANDs correctly against any category despite not fitting a signed int.\nexport const DEFAULT_COLLIDE_MASK = 0xFFFFFFFF;\n\n// Whether two bodies collide: each side's mask must accept the other's category. Symmetric by design, so the\n// outcome does not depend on which entity moved first; a one-sided response is done by checking `other` inside\n// onCollision. `collideCategory: 0` collides with nothing. `&` is signed, so compare against 0, not > 0.\nexport function canCollide(categoryA: number, maskA: number, categoryB: number, maskB: number): boolean {\n\treturn (maskA & categoryB) !== 0 && (maskB & categoryA) !== 0;\n}\n\n// A size loads a body, as it loads a transform: anything with a place in the world is collidable by default,\n// and the collide properties narrow that. All defining config from the entity template, so there is no `save`.\nexport interface BodyConfig {\n\t// Not read by the loader, only listed in `loadProperties`: they mark a config as having a place in the world.\n\twidth?: number\n\theight?: number\n\n\t// One of the SHAPE_ constants. Defaults to a circle for a `radius` config and a rectangle otherwise, so only\n\t// capsules name their shape.\n\tshape?: number\n\t// Not read here - the transform turns it into width/height - but a `radius` config decides the default shape.\n\tradius?: number\n\t// The bits this entity collides as, and the bits it collides with. See canCollide.\n\tcollideCategory?: number\n\tcollideMask?: number\n\t// Makes the body a sensor: found and reported, but never blocking or bounced off. Defaults to solid.\n\tsensor?: boolean\n}\n\n// Indexes into the backing Uint32Array block, exported because the broadphase reads the same offsets off the\n// raw shared block.\nexport const BODY_SHAPE_INDEX = 0;\nexport const BODY_CATEGORY_INDEX = 1;\nexport const BODY_MASK_INDEX = 2;\n// 1 for a sensor, 0 for solid. In the block, not a JS flag, so the broadphase and bounce read it on the worker.\nexport const BODY_SENSOR_INDEX = 3;\nexport const BODY_SIZE = 4;\n\n// Whether a body block is a sensor. Exported so a game system reads the flag the way the broadphase does.\nexport function isSensor(body: Uint32Array): boolean {\n\treturn body[BODY_SENSOR_INDEX] !== 0;\n}\n\nexport const bodyDefinition: ComponentDefinition<BodyComponent, Uint32Array, BodyConfig> = {\n\ttype: Uint32Array,\n\tsize: BODY_SIZE,\n\tloadProperties: ['width', 'height', 'radius', 'shape', 'collideCategory', 'collideMask', 'sensor'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([\n\t\t\ttoShape(config),\n\t\t\tconfig.collideCategory ?? DEFAULT_COLLIDE_CATEGORY,\n\t\t\tconfig.collideMask ?? DEFAULT_COLLIDE_MASK,\n\t\t\tconfig.sensor ? 1 : 0,\n\t\t]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget shape() {\n\t\t\t\treturn block[BODY_SHAPE_INDEX];\n\t\t\t},\n\t\t\tset shape(value: number) {\n\t\t\t\tblock[BODY_SHAPE_INDEX] = value;\n\t\t\t},\n\t\t\tget collideCategory() {\n\t\t\t\treturn block[BODY_CATEGORY_INDEX];\n\t\t\t},\n\t\t\tset collideCategory(value: number) {\n\t\t\t\tblock[BODY_CATEGORY_INDEX] = value;\n\t\t\t},\n\t\t\tget collideMask() {\n\t\t\t\treturn block[BODY_MASK_INDEX];\n\t\t\t},\n\t\t\tset collideMask(value: number) {\n\t\t\t\tblock[BODY_MASK_INDEX] = value;\n\t\t\t},\n\t\t\tget sensor() {\n\t\t\t\treturn block[BODY_SENSOR_INDEX] !== 0;\n\t\t\t},\n\t\t\tset sensor(value: boolean) {\n\t\t\t\tblock[BODY_SENSOR_INDEX] = value ? 1 : 0;\n\t\t\t},\n\t\t};\n\t},\n};\n\n// An unknown shape throws rather than defaulting: colliding with the wrong outline is harder to spot than a\n// failed load. A config that names no shape is read off its size - `radius` is a circle, anything else a rectangle.\nfunction toShape(config: BodyConfig): number {\n\tconst shape = config.shape;\n\tif(shape === undefined) {\n\t\treturn config.radius !== undefined ? SHAPE_CIRCLE : SHAPE_RECTANGLE;\n\t} else if(!KNOWN_SHAPES.includes(shape)) {\n\t\tthrow new Error(`Unknown body shape: ${shape}`);\n\t}\n\n\treturn shape;\n}\n\nexport default bodyDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// How much speed an entity keeps when it bounces, as a fraction of the velocity that pointed into what it hit:\n// 1 is a perfect bounce, 0.5 loses half on each, 0 does not turn around at all. See the bounce in\n// createPhysicsUpdate. Only bouncing entities carry this; one without it is still stopped by what it runs into\n// (the sweep does that for everything), it just does not rebound.\nexport interface BouncinessComponent {\n\tindex: number\n\tbounciness: number\n}\n\n// A `bounciness` config loads the component. Defining config from the entity template, so there is no `save`.\nexport interface BouncinessConfig {\n\tbounciness?: number\n}\n\n// Index into the backing Float32Array block, exported because the bounce reads the same offset off the raw block.\nexport const BOUNCINESS_INDEX = 0;\nexport const BOUNCINESS_SIZE = 1;\n\nexport const bouncinessDefinition: ComponentDefinition<BouncinessComponent, Float32Array, BouncinessConfig> = {\n\ttype: Float32Array,\n\tsize: BOUNCINESS_SIZE,\n\tloadProperties: ['bounciness'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.bounciness ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget bounciness() {\n\t\t\t\treturn block[BOUNCINESS_INDEX];\n\t\t\t},\n\t\t\tset bounciness(value: number) {\n\t\t\t\tblock[BOUNCINESS_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n};\n\nexport default bouncinessDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\nimport type { TransformComponent } from './transform-component';\n\n// Where an entity should be drawn, which diverges from where it is once physics runs less often than the screen\n// refreshes: a 50ms step against a 16ms frame updates the transform one frame in three, so drawing straight off\n// it stutters. This block holds a position that changes every frame instead.\n//\n// Filled by InterpolationSystem by blending between the positions before and after the last physics step, so\n// every position here is one the simulation produced, at the cost of one step of latency. Nothing in the\n// simulation reads it back - collision, the sweep and the spatial index all use the transform - which also keeps\n// it safe under lockstep multiplayer, where different frame rates would otherwise read different numbers.\nexport interface InterpolationComponent {\n\tindex: number\n\t// The render position, rewritten every frame by InterpolationSystem. A game reads it, and writes it only\n\t// through `snapEntity` after a teleport.\n\tx: number\n\ty: number\n\t// Where the entity stood before the step being blended. Written by the physics update.\n\tprevX: number\n\tprevY: number\n}\n\n// `interpolate` gives an entity this component, opt-in rather than automatic because it is 20 bytes and a\n// per-frame visit for something a game may never draw. `x`/`y` come off the same config the transform loads\n// from, so a render position starts where the entity spawned.\nexport interface InterpolationConfig {\n\tinterpolate?: boolean\n\tx?: number\n\ty?: number\n}\n\n// Indexes into the backing Float32Array block, exported because the physics and interpolation updates read\n// these offsets off the raw shared block.\nexport const INTERPOLATION_X_INDEX = 0;\nexport const INTERPOLATION_Y_INDEX = 1;\nexport const INTERPOLATION_PREV_X_INDEX = 2;\nexport const INTERPOLATION_PREV_Y_INDEX = 3;\n// How far along the current segment has been drawn: 0 at `prev`, 1 at the transform. A fraction, not\n// milliseconds, so leftover carries onto the next segment without remembering the old segment's length (segments\n// vary - see INTERPOLATION_DURATION_INDEX). Written only by the interpolation system.\nexport const INTERPOLATION_PROGRESS_INDEX = 4;\n// The step the interpolation system last reconciled against, to tell an already-started step from a new one.\nexport const INTERPOLATION_SYNCED_TICK_INDEX = 5;\n// How much simulated time this segment covers, in ms - the run's `elapsedTime`. Nearly always the step, but not\n// assumed to be: a late run banks more than a step and the next covers two, and dividing by the fixed step\n// would draw that at double speed. Written by the physics update.\nexport const INTERPOLATION_DURATION_INDEX = 6;\n// The physics step this block's `prev` belongs to, written last with a release store. A publication stamp: a\n// reader seeing the same tick on both sides of its own reads knows the `prev` and transform belong together,\n// and would otherwise draw the entity moving backwards. Also the arrival signal, which is why pacing lives here\n// rather than in the physics accumulator: that resets when a run is posted, this changes when it lands.\nexport const INTERPOLATION_TICK_INDEX = 7;\nexport const INTERPOLATION_SIZE = 8;\n\nexport const interpolationDefinition: ComponentDefinition<InterpolationComponent, Float32Array, InterpolationConfig> = {\n\ttype: Float32Array,\n\tsize: INTERPOLATION_SIZE,\n\tloadProperties: ['interpolate'],\n\tload(entity, memory, config) {\n\t\tconst x = config.x ?? 0;\n\t\tconst y = config.y ?? 0;\n\n\t\t// Spawn position on both sides of the blend, pacing at rest, so an entity added between steps is drawn\n\t\t// standing still. PhysicsSystem's first run stamps tick 1, so the 0 here reads as a new step to reconcile.\n\t\t// Duration 0 means \"no segment yet\", drawing the entity at its transform - as for anything never moved.\n\t\tconst index = memory.create([x, y, x, y, 0, 0, 0, 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget x() {\n\t\t\t\treturn block[INTERPOLATION_X_INDEX];\n\t\t\t},\n\t\t\tset x(value: number) {\n\t\t\t\tblock[INTERPOLATION_X_INDEX] = value;\n\t\t\t},\n\t\t\tget y() {\n\t\t\t\treturn block[INTERPOLATION_Y_INDEX];\n\t\t\t},\n\t\t\tset y(value: number) {\n\t\t\t\tblock[INTERPOLATION_Y_INDEX] = value;\n\t\t\t},\n\t\t\tget prevX() {\n\t\t\t\treturn block[INTERPOLATION_PREV_X_INDEX];\n\t\t\t},\n\t\t\tset prevX(value: number) {\n\t\t\t\tblock[INTERPOLATION_PREV_X_INDEX] = value;\n\t\t\t},\n\t\t\tget prevY() {\n\t\t\t\treturn block[INTERPOLATION_PREV_Y_INDEX];\n\t\t\t},\n\t\t\tset prevY(value: number) {\n\t\t\t\tblock[INTERPOLATION_PREV_Y_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n\t// No `save`: everything here is derived from the transform and rebuilt on the next step, and `interpolate`\n\t// is defining config from the entity template.\n};\n\n// Anything with the two components this reads, written structurally so it does not need the game's component map.\nexport interface SnappableEntity {\n\tcomponents: {\n\t\ttransform?: TransformComponent\n\t\tinterpolation?: InterpolationComponent\n\t}\n}\n\n// Drops the segment an entity is part way along and puts drawn and simulated position at the same place. Call\n// after writing a transform directly: a teleport by assigning `transform.x` otherwise reads as a long move and\n// is drawn sliding the whole way, since a long move and a teleport are the same two numbers. Not needed for\n// anything physics did - those are steps the simulation took and are drawn as they happened.\nexport function snapEntity(entity: SnappableEntity): void {\n\tconst { transform, interpolation } = entity.components;\n\tif(!transform || !interpolation) {\n\t\treturn;\n\t}\n\n\tinterpolation.prevX = interpolation.x = transform.x;\n\tinterpolation.prevY = interpolation.y = transform.y;\n}\n\nexport default interpolationDefinition;\n","import bodyDefinition, { type BodyComponent } from './body-component';\nimport bouncinessDefinition, { type BouncinessComponent } from './bounciness-component';\nimport interpolationDefinition, { type InterpolationComponent } from './interpolation-component';\nimport transformDefinition, { type TransformComponent } from './transform-component';\nimport velocityDefinition, { type VelocityComponent } from './velocity-component';\n\n// The component definitions this library supplies. A game spreads them into its own registry:\n//\n// const registry = { ...physicsRegistry, health: healthDefinition };\n//\n// The keys here are what the physics systems query on, so they must stay as the registry keys.\nexport const physicsRegistry = {\n\ttransform: transformDefinition,\n\tvelocity: velocityDefinition,\n\tbody: bodyDefinition,\n\tbounciness: bouncinessDefinition,\n\tinterpolation: interpolationDefinition,\n};\n\n// The slice of a game's component map the physics systems need. A type alias, not an interface, so it satisfies\n// the ECS's `ComponentMap` index signature; systems only require the game's map extends this.\nexport type PhysicsComponents = {\n\ttransform: TransformComponent\n\tvelocity: VelocityComponent\n\tbody: BodyComponent\n\tbounciness: BouncinessComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The strictly smaller slice InterpolationSystem needs: it never looks at velocity or body. Split out so the\n// interpolation update declares exactly what it touches.\nexport type InterpolationComponents = {\n\ttransform: TransformComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The same components as their backing arrays, the form an update sees on the worker thread. `body`, `bounciness`\n// and `interpolation` are optional because an entity may lack them; the collidable query requires `body`, so\n// CollisionComponents narrows it back to guaranteed there. A game widens this for extra callback components:\n//\n// type GameUpdateComponents = PhysicsUpdateComponents & { health?: Float32Array };\nexport type PhysicsUpdateComponents = {\n\ttransform: Float32Array\n\tvelocity: Float32Array\n\tbody?: Uint32Array\n\tbounciness?: Float32Array\n\tinterpolation?: Float32Array\n\t// For checking if entity is dead during run\n\tentity?: Uint32Array\n};\n\n// The blocks the interpolation update works on. Both required - the query asks for both - so no per-entity guard.\nexport type InterpolationUpdateComponents = {\n\ttransform: Float32Array\n\tinterpolation: Float32Array\n};\n","// Pure geometry for the shapes a transform plus a body describe, centred on (x, y) and turned `angle` radians\n// counter-clockwise. Loose number arguments rather than objects so the collision sweep can call per pair\n// without allocating.\n//\n// The three shapes are one idea: an oriented core grown by a radius.\n// rectangle - a width x height box, radius 0\n// circle - a point, radius width / 2\n// capsule - a segment along its facing, radius height / 2\n// A circle is a capsule whose segment has no length, so there are only two core kinds - box and segment - and\n// three pair tests rather than one per shape combination. See shapesOverlap.\n\nimport { SHAPE_CAPSULE, SHAPE_CIRCLE, SHAPE_RECTANGLE } from '../components/body-component';\n\nexport interface Vector {\n\tx: number\n\ty: number\n}\n\n// Scratch, reused rather than allocated: every user is a single unbroken pass, never re-entered part way through.\nconst GAP: Vector = { x: 0, y: 0 };\nconst CANDIDATE: Vector = { x: 0, y: 0 };\n\n// Clamped at 0 so a capsule shorter than it is wide collapses to a circle rather than turning inside out.\nexport function capsuleHalfLength(width: number, height: number): number {\n\treturn Math.max(0, (width - height) / 2);\n}\n\nexport function shapeRadius(shape: number, width: number, height: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn height / 2;\n\t}\n\n\treturn 0;\n}\n\n// A shape with no area never overlaps anything, which keeps a sizeless entity out of collisions. Which size\n// matters is per shape: a circle is nothing without its width, a capsule with no thickness is a bare line.\nexport function shapeIsEmpty(shape: number, width: number, height: number): boolean {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width <= 0;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn height <= 0;\n\t}\n\n\treturn width <= 0 || height <= 0;\n}\n\n// Half the axis-aligned box that fully contains a shape at any rotation - what the broadphase indexes it under,\n// so it must hold the whole shape. Asked per shape because a circle is its width alone: one whose height was\n// hand-written smaller is taller than the width/height rectangle a capsule or radius-loaded circle fits in.\nexport function shapeHalfWidth(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn Math.abs(Math.cos(angle)) * capsuleHalfLength(width, height) + height / 2;\n\t}\n\n\treturn boundsHalfWidth(width, height, angle);\n}\nexport function shapeHalfHeight(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn Math.abs(Math.sin(angle)) * capsuleHalfLength(width, height) + height / 2;\n\t}\n\n\treturn boundsHalfHeight(width, height, angle);\n}\n\n// Whether two shapes of any kind overlap. Every pair is one of three cases: box+box (separating axis),\n// round+round (segment gap under the summed radii), or box+round (segment gap under the one radius). Touching\n// exactly counts as not overlapping throughout - the comparisons are strict.\nexport function shapesOverlap(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n): boolean {\n\tif(shapeIsEmpty(aShape, aWidth, aHeight) || shapeIsEmpty(bShape, bWidth, bHeight)) {\n\t\treturn false;\n\t}\n\n\tconst aIsRound = aShape !== SHAPE_RECTANGLE;\n\tconst bIsRound = bShape !== SHAPE_RECTANGLE;\n\n\tif(!aIsRound && !bIsRound) {\n\t\treturn orientedBoxesOverlap(aX, aY, aWidth, aHeight, aAngle, bX, bY, bWidth, bHeight, bAngle);\n\t} else if(aIsRound && !bIsRound) {\n\t\t// The mixed case is written once with the box first and reached from the other side by swapping.\n\t\treturn shapesOverlap(\n\t\t\tbShape, bX, bY, bWidth, bHeight, bAngle,\n\t\t\taShape, aX, aY, aWidth, aHeight, aAngle,\n\t\t);\n\t}\n\n\tconst bHalf = coreHalfLength(bShape, bWidth, bHeight);\n\tconst bCos = Math.cos(bAngle) * bHalf;\n\tconst bSin = Math.sin(bAngle) * bHalf;\n\tconst bRadius = shapeRadius(bShape, bWidth, bHeight);\n\n\tif(!aIsRound) {\n\t\treturn segmentBoxDistanceSquared(\n\t\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t\t\taX, aY, aWidth, aHeight, aAngle,\n\t\t) < bRadius * bRadius;\n\t}\n\n\tconst aHalf = coreHalfLength(aShape, aWidth, aHeight);\n\tconst aCos = Math.cos(aAngle) * aHalf;\n\tconst aSin = Math.sin(aAngle) * aHalf;\n\tconst reach = shapeRadius(aShape, aWidth, aHeight) + bRadius;\n\n\treturn segmentSegmentDistanceSquared(\n\t\taX - aCos, aY - aSin, aX + aCos, aY + aSin,\n\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t) < reach * reach;\n}\n\n// How far a shape's core reaches from its centre: a capsule's segment, and nothing for a circle or rectangle.\nfunction coreHalfLength(shape: number, width: number, height: number): number {\n\treturn shape === SHAPE_CAPSULE ? capsuleHalfLength(width, height) : 0;\n}\n\n// The unit direction a is pushed off b at their contact, written into `out`; false when the two are on top of\n// each other and there is no such direction. Split the same three ways as shapesOverlap and off the same\n// geometry, so the normal is the face that was actually hit rather than a guess from the bounding boxes: a pair\n// meeting at an angle reflects along the contact, not along whichever world axis happens to be nearest.\nexport function contactNormal(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aIsRound = aShape !== SHAPE_RECTANGLE;\n\tconst bIsRound = bShape !== SHAPE_RECTANGLE;\n\n\tif(!aIsRound && !bIsRound) {\n\t\treturn boxesContactNormal(aX, aY, aWidth, aHeight, aAngle, bX, bY, bWidth, bHeight, bAngle, out);\n\t} else if(aIsRound && bIsRound) {\n\t\treturn roundsContactNormal(aShape, aX, aY, aWidth, aHeight, aAngle, bShape, bX, bY, bWidth, bHeight, bAngle, out);\n\t} else if(aIsRound) {\n\t\treturn boxRoundContactNormal(bX, bY, bWidth, bHeight, bAngle, aShape, aX, aY, aWidth, aHeight, aAngle, out);\n\t}\n\n\t// The mixed case is written once with the box second and reached from the other side by turning it round.\n\tif(!boxRoundContactNormal(aX, aY, aWidth, aHeight, aAngle, bShape, bX, bY, bWidth, bHeight, bAngle, out)) {\n\t\treturn false;\n\t}\n\n\tout.x = -out.x;\n\tout.y = -out.y;\n\n\treturn true;\n}\n\n// Two boxes part along whichever of their four side directions they run into each other least far - the face\n// that was hit. The same axes and reaches the separating-axis test uses, kept as depths rather than a verdict.\nfunction boxesContactNormal(\n\taX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aCos = Math.cos(aAngle);\n\tconst aSin = Math.sin(aAngle);\n\tconst bCos = Math.cos(bAngle);\n\tconst bSin = Math.sin(bAngle);\n\tconst aHalfWidth = aWidth / 2;\n\tconst aHalfHeight = aHeight / 2;\n\tconst bHalfWidth = bWidth / 2;\n\tconst bHalfHeight = bHeight / 2;\n\tconst offsetX = aX - bX;\n\tconst offsetY = aY - bY;\n\n\tlet best = boxOverlapAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tout.x = aCos;\n\tout.y = aSin;\n\n\tlet depth = boxOverlapAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = -aSin;\n\t\tout.y = aCos;\n\t}\n\n\tdepth = boxOverlapAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = bCos;\n\t\tout.y = bSin;\n\t}\n\n\tdepth = boxOverlapAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tout.x = -bSin;\n\t\tout.y = bCos;\n\t}\n\n\tpointAwayFrom(offsetX, offsetY, out);\n\n\treturn true;\n}\n\n// Two rounds meet where their cores come closest - the line between those points, grown by nothing since both\n// radii lie along it. Two circles reduce to the line between their centres.\nfunction roundsContactNormal(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aHalf = coreHalfLength(aShape, aWidth, aHeight);\n\tconst aCos = Math.cos(aAngle) * aHalf;\n\tconst aSin = Math.sin(aAngle) * aHalf;\n\tconst bHalf = coreHalfLength(bShape, bWidth, bHeight);\n\tconst bCos = Math.cos(bAngle) * bHalf;\n\tconst bSin = Math.sin(bAngle) * bHalf;\n\n\tconst distance = Math.sqrt(segmentSegmentDistanceSquared(\n\t\taX - aCos, aY - aSin, aX + aCos, aY + aSin,\n\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t\tGAP,\n\t));\n\tif(distance > 0) {\n\t\tout.x = GAP.x / distance;\n\t\tout.y = GAP.y / distance;\n\n\t\treturn true;\n\t}\n\n\t// The cores cross, so they have no gap to part along; the centre line is the best left.\n\treturn normalize(aX - bX, aY - bY, out);\n}\n\n// A round shape leaves a box along the gap between its core and the box - the box's face for a flat hit, its\n// corner for one off the end - which is the direction the round side is pushed. Not the box: the caller turns\n// it round for that.\nfunction boxRoundContactNormal(\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\n\troundShape: number, roundX: number, roundY: number, roundWidth: number, roundHeight: number, roundAngle: number,\n\tout: Vector,\n): boolean {\n\tconst half = coreHalfLength(roundShape, roundWidth, roundHeight);\n\tconst halfX = Math.cos(roundAngle) * half;\n\tconst halfY = Math.sin(roundAngle) * half;\n\n\tconst distance = Math.sqrt(segmentBoxDistanceSquared(\n\t\troundX - halfX, roundY - halfY, roundX + halfX, roundY + halfY,\n\t\tboxX, boxY, boxWidth, boxHeight, boxAngle,\n\t\tGAP,\n\t));\n\tif(distance > 0) {\n\t\tout.x = GAP.x / distance;\n\t\tout.y = GAP.y / distance;\n\n\t\treturn true;\n\t}\n\n\t// The core runs through the box, leaving no gap to point along. Fall back to the shallowest way out, in the\n\t// box's frame where it is axis aligned: its own two sides, or square across the core.\n\tconst cos = Math.cos(boxAngle);\n\tconst sin = Math.sin(boxAngle);\n\tconst offsetX = roundX - boxX;\n\tconst offsetY = roundY - boxY;\n\tconst localX = offsetX * cos + offsetY * sin;\n\tconst localY = -offsetX * sin + offsetY * cos;\n\tconst localHalfX = halfX * cos + halfY * sin;\n\tconst localHalfY = -halfX * sin + halfY * cos;\n\tconst radius = shapeRadius(roundShape, roundWidth, roundHeight);\n\tconst halfWidth = boxWidth / 2;\n\tconst halfHeight = boxHeight / 2;\n\n\tlet best = roundOverlapAlong(1, 0, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\tout.x = 1;\n\tout.y = 0;\n\n\tlet depth = roundOverlapAlong(0, 1, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = 0;\n\t\tout.y = 1;\n\t}\n\n\tif(normalize(-localHalfY, localHalfX, CANDIDATE)) {\n\t\tdepth = roundOverlapAlong(CANDIDATE.x, CANDIDATE.y, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\t\tif(depth < best) {\n\t\t\tout.x = CANDIDATE.x;\n\t\t\tout.y = CANDIDATE.y;\n\t\t}\n\t}\n\n\tpointAwayFrom(localX, localY, out);\n\n\t// Back out of the box's frame.\n\tconst axisX = out.x;\n\tconst axisY = out.y;\n\tout.x = axisX * cos - axisY * sin;\n\tout.y = axisX * sin + axisY * cos;\n\n\treturn true;\n}\n\n// How far an axis-aligned box at the origin and a segment grown by `radius` run into each other along a unit\n// axis. The round side reaches its core's shadow plus the radius, which is the same in every direction.\nfunction roundOverlapAlong(\n\taxisX: number, axisY: number,\n\tcentreX: number, centreY: number,\n\thalfX: number, halfY: number, radius: number,\n\thalfWidth: number, halfHeight: number,\n): number {\n\tconst boxReach = halfWidth * Math.abs(axisX) + halfHeight * Math.abs(axisY);\n\tconst roundReach = Math.abs(halfX * axisX + halfY * axisY) + radius;\n\n\treturn boxReach + roundReach - Math.abs(centreX * axisX + centreY * axisY);\n}\n\n// Turns an axis the way that leads away from the other shape. A tie means the centres sit on the axis' line, and\n// either side is as good.\nfunction pointAwayFrom(offsetX: number, offsetY: number, out: Vector): void {\n\tif(offsetX * out.x + offsetY * out.y < 0) {\n\t\tout.x = -out.x;\n\t\tout.y = -out.y;\n\t}\n}\n\nfunction normalize(x: number, y: number, out: Vector): boolean {\n\tconst length = Math.sqrt(x * x + y * y);\n\tif(length === 0) {\n\t\treturn false;\n\t}\n\n\tout.x = x / length;\n\tout.y = y / length;\n\n\treturn true;\n}\n\n// Half the axis-aligned box containing a rotated rectangle: each side contributes however much now points along\n// the axis. width / 2 at angle 0, height / 2 at a quarter turn.\nexport function boundsHalfWidth(width: number, height: number, angle: number): number {\n\treturn (Math.abs(Math.cos(angle)) * width + Math.abs(Math.sin(angle)) * height) / 2;\n}\nexport function boundsHalfHeight(width: number, height: number, angle: number): number {\n\treturn (Math.abs(Math.sin(angle)) * width + Math.abs(Math.cos(angle)) * height) / 2;\n}\n\n// Whether two rotated boxes overlap, by the separating axis theorem: for two boxes only four axes can separate\n// them - the two each box's sides face along. Touching exactly and a zero-area box both count as not\n// overlapping, which keeps a sizeless entity out of collisions.\nexport function orientedBoxesOverlap(\n\taX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n): boolean {\n\tif(aWidth <= 0 || aHeight <= 0 || bWidth <= 0 || bHeight <= 0) {\n\t\treturn false;\n\t}\n\n\tconst aCos = Math.cos(aAngle);\n\tconst aSin = Math.sin(aAngle);\n\tconst bCos = Math.cos(bAngle);\n\tconst bSin = Math.sin(bAngle);\n\n\tconst aHalfWidth = aWidth / 2;\n\tconst aHalfHeight = aHeight / 2;\n\tconst bHalfWidth = bWidth / 2;\n\tconst bHalfHeight = bHeight / 2;\n\n\tconst offsetX = bX - aX;\n\tconst offsetY = bY - aY;\n\n\t// a's two axes, then b's; each box's sides face along (cos, sin) and (-sin, cos) of its angle.\n\treturn boxOverlapAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0;\n}\n\n// How far the boxes' shadows on the axis run into each other; zero or less means the axis separates them, so\n// touching exactly does not overlap. The axes are always a box's own unit-vector sides, so the dot products are\n// true distances and nothing needs normalising. The depth is what picks the contact face in contactNormal.\nfunction boxOverlapAlong(\n\taxisX: number, axisY: number,\n\toffsetX: number, offsetY: number,\n\taHalfWidth: number, aHalfHeight: number, aCos: number, aSin: number,\n\tbHalfWidth: number, bHalfHeight: number, bCos: number, bSin: number,\n): number {\n\tconst aReach = aHalfWidth * Math.abs(aCos * axisX + aSin * axisY) + aHalfHeight * Math.abs(-aSin * axisX + aCos * axisY);\n\tconst bReach = bHalfWidth * Math.abs(bCos * axisX + bSin * axisY) + bHalfHeight * Math.abs(-bSin * axisX + bCos * axisY);\n\n\treturn aReach + bReach - Math.abs(offsetX * axisX + offsetY * axisY);\n}\n\n// Squared distance between two segments - squared so callers can compare against a squared radius without a\n// sqrt. Settles every round pair (a circle is a segment whose ends coincide). Solves the closest points on the\n// two infinite lines then clamps back into the segments; the parallel case pins one end and clamps the rest.\n// `out`, if given, is filled with the vector from b's closest point to a's - the direction a is pushed off b.\nexport function segmentSegmentDistanceSquared(\n\taX1: number, aY1: number, aX2: number, aY2: number,\n\tbX1: number, bY1: number, bX2: number, bY2: number,\n\tout?: Vector,\n): number {\n\tconst aDeltaX = aX2 - aX1;\n\tconst aDeltaY = aY2 - aY1;\n\tconst bDeltaX = bX2 - bX1;\n\tconst bDeltaY = bY2 - bY1;\n\tconst offsetX = aX1 - bX1;\n\tconst offsetY = aY1 - bY1;\n\n\tconst aLength = aDeltaX * aDeltaX + aDeltaY * aDeltaY;\n\tconst bLength = bDeltaX * bDeltaX + bDeltaY * bDeltaY;\n\tconst bOffset = bDeltaX * offsetX + bDeltaY * offsetY;\n\n\tlet aParam = 0;\n\tlet bParam = 0;\n\tif(aLength <= 0 && bLength <= 0) {\n\t\tif(out) {\n\t\t\tout.x = offsetX;\n\t\t\tout.y = offsetY;\n\t\t}\n\n\t\treturn offsetX * offsetX + offsetY * offsetY;\n\t} else if(aLength <= 0) {\n\t\tbParam = clamp(bOffset / bLength);\n\t} else {\n\t\tconst aOffset = aDeltaX * offsetX + aDeltaY * offsetY;\n\t\tif(bLength <= 0) {\n\t\t\taParam = clamp(-aOffset / aLength);\n\t\t} else {\n\t\t\tconst along = aDeltaX * bDeltaX + aDeltaY * bDeltaY;\n\t\t\t// Zero when the segments are parallel; pinning aParam at 0 then picks any point, all equally close.\n\t\t\tconst denominator = aLength * bLength - along * along;\n\t\t\tif(denominator !== 0) {\n\t\t\t\taParam = clamp((along * bOffset - aOffset * bLength) / denominator);\n\t\t\t}\n\n\t\t\tbParam = (along * aParam + bOffset) / bLength;\n\t\t\t// Clamping bParam moves b's closest point, so aParam has to be solved again.\n\t\t\tif(bParam < 0) {\n\t\t\t\tbParam = 0;\n\t\t\t\taParam = clamp(-aOffset / aLength);\n\t\t\t} else if(bParam > 1) {\n\t\t\t\tbParam = 1;\n\t\t\t\taParam = clamp((along - aOffset) / aLength);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst gapX = (aX1 + aDeltaX * aParam) - (bX1 + bDeltaX * bParam);\n\tconst gapY = (aY1 + aDeltaY * aParam) - (bY1 + bDeltaY * bParam);\n\tif(out) {\n\t\tout.x = gapX;\n\t\tout.y = gapY;\n\t}\n\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Squared distance between a segment and a rotated box, measured in the box's frame where it is axis aligned.\n// Two disjoint convex shapes have their closest pair at a corner, so once the crossing case is ruled out the\n// answer is the nearest of each segment end to the box and each box corner to the segment.\n// `out`, if given, is filled with the world vector from the box's closest point to the segment's - the direction\n// the segment is pushed off the box. It is (0, 0) when the segment runs through the box, which has no such\n// direction; contactNormal falls back to a penetration axis there.\nexport function segmentBoxDistanceSquared(\n\tx1: number, y1: number, x2: number, y2: number,\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\n\tout?: Vector,\n): number {\n\tconst cos = Math.cos(boxAngle);\n\tconst sin = Math.sin(boxAngle);\n\tconst halfWidth = boxWidth / 2;\n\tconst halfHeight = boxHeight / 2;\n\n\tconst firstOffsetX = x1 - boxX;\n\tconst firstOffsetY = y1 - boxY;\n\tconst secondOffsetX = x2 - boxX;\n\tconst secondOffsetY = y2 - boxY;\n\tconst localX1 = firstOffsetX * cos + firstOffsetY * sin;\n\tconst localY1 = -firstOffsetX * sin + firstOffsetY * cos;\n\tconst localX2 = secondOffsetX * cos + secondOffsetY * sin;\n\tconst localY2 = -secondOffsetX * sin + secondOffsetY * cos;\n\n\t// A segment through the box has both ends outside and every corner off, so the nearest-pair search below\n\t// would report a real distance for two shapes already through each other; catch it first.\n\tif(!segmentMissesBox(localX1, localY1, localX2, localY2, halfWidth, halfHeight)) {\n\t\tif(out) {\n\t\t\tout.x = 0;\n\t\t\tout.y = 0;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif(!out) {\n\t\treturn Math.min(\n\t\t\tpointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight),\n\t\t\tpointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight),\n\t\t\tpointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\t);\n\t}\n\n\t// The same six candidates, keeping the gap of the winner. The corner ones measure box-to-segment, the other\n\t// way round from the endpoint ones, so they are negated to leave every gap pointing off the box.\n\tlet best = pointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight, out);\n\tbest = keepCloser(best, pointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight, CANDIDATE), CANDIDATE, out, false);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\n\t// Back out of the box's frame, the gap having been measured inside it.\n\tconst localGapX = out.x;\n\tconst localGapY = out.y;\n\tout.x = localGapX * cos - localGapY * sin;\n\tout.y = localGapX * sin + localGapY * cos;\n\n\treturn best;\n}\n\n// Takes the candidate over what `out` holds when it is closer, flipping it when it was measured box-to-segment.\nfunction keepCloser(best: number, candidate: number, gap: Vector, out: Vector, flip: boolean): number {\n\tif(candidate >= best) {\n\t\treturn best;\n\t}\n\n\tout.x = flip ? -gap.x : gap.x;\n\tout.y = flip ? -gap.y : gap.y;\n\n\treturn candidate;\n}\n\n// `out`, if given, is filled with the vector from the segment's closest point to (x, y).\nexport function pointSegmentDistanceSquared(x: number, y: number, x1: number, y1: number, x2: number, y2: number, out?: Vector): number {\n\tconst deltaX = x2 - x1;\n\tconst deltaY = y2 - y1;\n\tconst length = deltaX * deltaX + deltaY * deltaY;\n\n\tconst along = length > 0 ? clamp(((x - x1) * deltaX + (y - y1) * deltaY) / length) : 0;\n\tconst gapX = x - (x1 + deltaX * along);\n\tconst gapY = y - (y1 + deltaY * along);\n\tif(out) {\n\t\tout.x = gapX;\n\t\tout.y = gapY;\n\t}\n\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Whether an origin-centred axis-aligned box and a segment fail to reach, by separating axis. Touching counts\n// as reaching. For a zero-length segment the normal is (0, 0), which never separates and leaves the two box\n// axes to decide whether the point is inside.\nfunction segmentMissesBox(x1: number, y1: number, x2: number, y2: number, halfWidth: number, halfHeight: number): boolean {\n\tif(Math.max(x1, x2) < -halfWidth || Math.min(x1, x2) > halfWidth) {\n\t\treturn true;\n\t} else if(Math.max(y1, y2) < -halfHeight || Math.min(y1, y2) > halfHeight) {\n\t\treturn true;\n\t}\n\n\tconst normalX = -(y2 - y1);\n\tconst normalY = x2 - x1;\n\n\treturn Math.abs(normalX * x1 + normalY * y1) > halfWidth * Math.abs(normalX) + halfHeight * Math.abs(normalY);\n}\n\n// Squared distance from a point to an origin-centred axis-aligned box; 0 inside it. `out`, if given, is filled\n// with the vector from the box's closest point to (x, y).\nfunction pointBoxDistanceSquared(x: number, y: number, halfWidth: number, halfHeight: number, out?: Vector): number {\n\tconst gapX = Math.abs(x) - halfWidth;\n\tconst gapY = Math.abs(y) - halfHeight;\n\tconst outsideX = gapX > 0 ? gapX : 0;\n\tconst outsideY = gapY > 0 ? gapY : 0;\n\tif(out) {\n\t\tout.x = x < 0 ? -outsideX : outsideX;\n\t\tout.y = y < 0 ? -outsideY : outsideY;\n\t}\n\n\treturn outsideX * outsideX + outsideY * outsideY;\n}\n\nfunction clamp(value: number): number {\n\tif(value < 0) {\n\t\treturn 0;\n\t} else if(value > 1) {\n\t\treturn 1;\n\t}\n\n\treturn value;\n}\n","import Flatbush from 'flatbush';\nimport { DEAD_INDEX } from '@daneren2005/shared-memory-ecs';\nimport type { ComponentMap, ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents } from '@daneren2005/shared-memory-ecs';\nimport type { PhysicsUpdateComponents } from '../components/registry';\nimport { BODY_CATEGORY_INDEX, BODY_MASK_INDEX, BODY_SENSOR_INDEX, BODY_SHAPE_INDEX } from '../components/body-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport { shapeHalfHeight, shapeHalfWidth, shapeIsEmpty, shapesOverlap } from '../math/shapes';\n\n// How close to contact a sweep settles for, in world units: below anything a game would draw, yet reached by\n// the halving below in a handful of steps.\nconst CONTACT_TOLERANCE = 1e-4;\n// Cap on the halving so a huge move cannot spawn unbounded overlap tests; reaches the tolerance for any move\n// up to ~1600 units.\nconst MAX_REFINEMENTS = 24;\n\n// Holds everything with a transform and a body, not only the entities the system moves, so a mover can run\n// into a wall or station that has no velocity of its own.\nexport const COLLIDABLE_QUERY = 'collidable';\n\n// Blocks handed to a collision callback for the entity run into. `transform` and `body` are guaranteed by the\n// collidable query; `velocity` and anything in `optional` are present only if that entity has them.\nexport type CollisionComponents<T extends PhysicsUpdateComponents> = Partial<T> & {\n\ttransform: Float32Array\n\tbody: Uint32Array\n\tentity?: Uint32Array\n};\n\n// The entity doing the running into: the one being updated, so everything the system asked for is present.\nexport interface MovingEntity<T extends PhysicsUpdateComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// The entity on the other side of a collision, which may be something the system never moves.\nexport interface CollisionEntity<T extends PhysicsUpdateComponents> {\n\tentityId: number\n\tcomponents: CollisionComponents<T>\n}\n\n// What a game runs when one entity moves into another. Runs on the physics thread, so it is a plain function\n// over the raw blocks and reaches the main thread only through `callbacks`. Called once per pair per run, right\n// after the mover is put down: `self` is whichever side reached the contact first, `other` is the other. Because\n// the other side gets no call of its own, a callback has to decide for both. A velocity-less entity is never\n// `self` but is found as `other`.\nexport type CollisionFunction<C extends ComponentMap, T extends PhysicsUpdateComponents & EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tself: MovingEntity<T>,\n\tother: CollisionEntity<T>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\n\n// How much of a move an entity may make and what stopped it. `fraction` is how far along it got (1 clear, 0\n// already pressed against something). `blocking` is what it came to rest against - the overlap test there will\n// not report it, since the two are touching; more than one entry means it wedged between them at once.\nexport interface SweepResult<T extends PhysicsUpdateComponents> {\n\tfraction: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// How a move came out, as the displacement to apply rather than a fraction - a move that dropped an axis is no\n// longer a scaling of the original. `blocking` is empty for a move that got where it was going.\nexport interface MoveResult<T extends PhysicsUpdateComponents> {\n\tmoveX: number\n\tmoveY: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// One shared empty array for the sweeps that find nothing, rather than one per entity per run.\nconst NOTHING_BLOCKING: Array<never> = [];\n\n// Packs a pair of entity ids into one key. Ids must stay under this for the key to be unique, which a pool an\n// ECS can address leaves untouchable, and it keeps the product inside the safe integer range.\nconst MAX_PAIR_ID = 2 ** 26;\n\n// A per-axis sweep that hit nothing: the whole axis, clear. Held once rather than rebuilt per clear axis.\nconst CLEAR = { fraction: 1, blocking: NOTHING_BLOCKING };\n\n// The searching entity's blocks read out once, so a sweep testing the same pair repeatedly does not re-read\n// them. x/y are where it started the move.\ninterface Searcher {\n\tentityId: number\n\tshape: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n\thalfWidth: number\n\thalfHeight: number\n\tcategory: number\n\tmask: number\n\t// A sensor's move is never swept short: it passes through rather than resting against. Read once here.\n\tsensor: boolean\n}\n\n// One category's broadphase: every entity that collides as that category, plus an R-tree over them. A searcher\n// only visits buckets its mask accepts. Bucketed on the whole category value, so an entity that collides as two\n// things at once still lives in exactly one bucket and is never found twice by one search.\ninterface CategoryBucket<T extends PhysicsUpdateComponents> {\n\tcategory: number\n\tentries: Array<CollisionEntity<T>>\n\tindex: Flatbush\n}\n\n// An R-tree over every collidable entity, built once at the top of a run and then asked, per entity as it\n// moves, what it might have hit.\n//\n// Everything is indexed where it stood at the start of the run - the only moment all entities agree on. To stay\n// a superset of what can collide, each box is grown by how far that entity could travel this run, both ways: a\n// callback may turn an entity around before its own move, so a box grown only forwards would point wrong.\n//\n// Split by collide category so most of what an entity cannot hit is ruled out a subtree at a time. An empty\n// category has no bucket, so Flatbush is never asked to index zero items.\nexport default class CollisionBroadphase<T extends PhysicsUpdateComponents> {\n\tprivate buckets: Array<CategoryBucket<T>> = [];\n\t// Whether anything in this run can bounce. Bounces are resolved for both sides of a contact, so an entity with\n\t// no bounciness of its own still has to look at what it ran into - but only in a world where that can matter.\n\treadonly hasBounciness: boolean = false;\n\t// The pairs already resolved this run. Lives here rather than in the update because the tree is what a run is\n\t// scoped to: preRun builds a new one, so the set is empty again for the next run with nothing to clear.\n\tprivate resolved = new Set<number>();\n\n\tconstructor(entities: Array<{ entityId: number, components: EntityUpdateComponents }>, seconds: number) {\n\t\t// Collected per category first because Flatbush needs its item count up front, which is only known once\n\t\t// everything that cannot collide has been dropped.\n\t\tconst pending = new Map<number, { entries: Array<CollisionEntity<T>>, bounds: Array<number> }>();\n\n\t\tfor(const entity of entities) {\n\t\t\t// The ECS types blocks generically as ComponentTypedArray; narrow to the concrete arrays once here so\n\t\t\t// neither the search nor the game's callback needs a cast.\n\t\t\tconst components = entity.components as CollisionComponents<T>;\n\t\t\tconst transform = components.transform;\n\t\t\tconst body = components.body;\n\t\t\tif(!transform || !body) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// No mask can name category 0, so a category-0 entity can never be the other half of a collision;\n\t\t\t// dropping it here is exact and is what makes `collideCategory: 0` opt out entirely.\n\t\t\tconst category = body[BODY_CATEGORY_INDEX];\n\t\t\tif(category === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst shape = body[BODY_SHAPE_INDEX];\n\t\t\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\t\t\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\t\t\t// A shape with no area can never overlap anything; dropping it keeps a sizeless entity out of the tree.\n\t\t\tif(shapeIsEmpty(shape, width, height)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\t\t\tconst halfWidth = shapeHalfWidth(shape, width, height, angle);\n\t\t\tconst halfHeight = shapeHalfHeight(shape, width, height, angle);\n\n\t\t\tif(components.bounciness) {\n\t\t\t\tthis.hasBounciness = true;\n\t\t\t}\n\n\t\t\tconst velocity = components.velocity;\n\t\t\tconst travelX = velocity ? Math.abs(velocity[VELOCITY_X_INDEX]) * seconds : 0;\n\t\t\tconst travelY = velocity ? Math.abs(velocity[VELOCITY_Y_INDEX]) * seconds : 0;\n\n\t\t\tconst x = transform[TRANSFORM_X_INDEX];\n\t\t\tconst y = transform[TRANSFORM_Y_INDEX];\n\n\t\t\tlet bucket = pending.get(category);\n\t\t\tif(!bucket) {\n\t\t\t\tbucket = { entries: [], bounds: [] };\n\t\t\t\tpending.set(category, bucket);\n\t\t\t}\n\n\t\t\tbucket.entries.push({ entityId: entity.entityId, components });\n\t\t\tbucket.bounds.push(\n\t\t\t\tx - halfWidth - travelX,\n\t\t\t\ty - halfHeight - travelY,\n\t\t\t\tx + halfWidth + travelX,\n\t\t\t\ty + halfHeight + travelY,\n\t\t\t);\n\t\t}\n\n\t\tfor(const [category, { entries, bounds }] of pending) {\n\t\t\tconst index = new Flatbush(entries.length);\n\t\t\tfor(let i = 0; i < entries.length; i++) {\n\t\t\t\tindex.add(bounds[i * 4], bounds[i * 4 + 1], bounds[i * 4 + 2], bounds[i * 4 + 3]);\n\t\t\t}\n\t\t\tindex.finish();\n\n\t\t\tthis.buckets.push({ category, entries, index });\n\t\t}\n\t}\n\n\t// Claims a contact for the caller: true the first time this run sees the pair, false every time after. Both\n\t// sides find the same contact - one sweeping into it, the other overlapping it on its own turn - and it is\n\t// resolved once, by whichever got there first, so nothing is bounced or reported twice.\n\tclaimContact(a: number, b: number): boolean {\n\t\tconst key = a < b ? a * MAX_PAIR_ID + b : b * MAX_PAIR_ID + a;\n\t\tif(this.resolved.has(key)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.resolved.add(key);\n\n\t\treturn true;\n\t}\n\n\t// Runs `handle` for everything `self` really overlaps, having just moved. The tree narrows the field, then\n\t// each candidate gets the full rotated box test against where both entities are now - which for one that has\n\t// not moved yet is still its start. That evens out: the pair is seen again from the other side once it moves.\n\tforEachOverlapping(self: MovingEntity<T>, handle: (other: CollisionEntity<T>) => void): void {\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { x, y, halfWidth, halfHeight } = searcher;\n\t\t// Searched ungrown at where this entity ended up: every tree box already allows for its own entity's move.\n\t\tthis.forEachCandidate(searcher, x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight, other => {\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\thandle(other);\n\t\t\t}\n\t\t});\n\t}\n\n\t// How much of the move `self` may take before it runs into something. The whole move is checked in one pass\n\t// for what it lands on, and only then is the resting place refined - so the answer is the earliest contact\n\t// of the lot, not whichever candidate came up first.\n\tsweep(self: MovingEntity<T>, moveX: number, moveY: number): SweepResult<T> {\n\t\tconst distance = Math.sqrt(moveX * moveX + moveY * moveY);\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher || distance === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\tconst candidates = this.gatherCandidates(searcher, moveX, moveY);\n\t\tif(candidates.length === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\treturn this.refine(searcher, candidates, moveX, moveY, distance);\n\t}\n\n\t// Resolves a move as the displacement to write. With `slide` off it is `sweep` in other clothes: the whole\n\t// move, or the swept-short part. With `slide` on, a blocked diagonal is resolved one axis at a time so an\n\t// entity clipping a corner keeps running along the wall instead of sticking to it. Sliding is the caller's to\n\t// ask for: it is wrong for a bouncing entity, which turns around rather than skating along, so bounces pass\n\t// `slide` off.\n\tresolveMove(self: MovingEntity<T>, moveX: number, moveY: number, slide: boolean): MoveResult<T> {\n\t\tconst distance = Math.sqrt(moveX * moveX + moveY * moveY);\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher || distance === 0) {\n\t\t\treturn { moveX, moveY, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\tconst candidates = this.gatherCandidates(searcher, moveX, moveY);\n\t\tif(candidates.length === 0) {\n\t\t\treturn { moveX, moveY, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\t// Blocked diagonal, asked to slide: resolve each axis on its own to find which one was actually blocked.\n\t\tif(slide && moveX !== 0 && moveY !== 0) {\n\t\t\tconst xCandidates = this.gatherCandidates(searcher, moveX, 0);\n\t\t\tconst yCandidates = this.gatherCandidates(searcher, 0, moveY);\n\n\t\t\t// Corner clip: each axis alone is clear, so only the diagonal was caught. Slide the full length of the\n\t\t\t// dominant axis and leave the other - advancing it would walk back into the corner.\n\t\t\tif(xCandidates.length === 0 && yCandidates.length === 0) {\n\t\t\t\treturn Math.abs(moveX) >= Math.abs(moveY)\n\t\t\t\t\t? { moveX, moveY: 0, blocking: NOTHING_BLOCKING }\n\t\t\t\t\t: { moveX: 0, moveY, blocking: NOTHING_BLOCKING };\n\t\t\t}\n\n\t\t\t// At least one axis is blocked. Take each as far as it goes - a blocked one refined up to its edge, not\n\t\t\t// dropped to nothing - so the entity ends hard against the obstacle.\n\t\t\tconst x = xCandidates.length === 0 ? CLEAR : this.refine(searcher, xCandidates, moveX, 0, Math.abs(moveX));\n\t\t\tconst y = yCandidates.length === 0 ? CLEAR : this.refine(searcher, yCandidates, 0, moveY, Math.abs(moveY));\n\n\t\t\treturn { moveX: moveX * x.fraction, moveY: moveY * y.fraction, blocking: mergeBlocking(x.blocking, y.blocking) };\n\t\t}\n\n\t\t// Not sliding, or a straight move: come to rest where it stops.\n\t\tconst rest = this.refine(searcher, candidates, moveX, moveY, distance);\n\n\t\treturn { moveX: moveX * rest.fraction, moveY: moveY * rest.fraction, blocking: rest.blocking };\n\t}\n\n\t// The cheap half of a sweep: everything the move would land on, tested only where it ends. That keeps a clear\n\t// move - nearly every move - to one shape test per candidate. The trade is that a move long enough to carry\n\t// an entity clean past something is not stopped by it, which a fixed `deltaBetweenRuns` rules out.\n\tprivate gatherCandidates(searcher: Searcher, moveX: number, moveY: number): Array<CollisionEntity<T>> {\n\t\tconst { x, y, halfWidth, halfHeight } = searcher;\n\n\t\t// A sensor's move is blocked by nothing; it is still reported by forEachOverlapping, but never gathered.\n\t\tif(searcher.sensor) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst endX = x + moveX;\n\t\tconst endY = y + moveY;\n\n\t\tconst candidates: Array<CollisionEntity<T>> = [];\n\t\tthis.forEachCandidate(searcher, endX - halfWidth, endY - halfHeight, endX + halfWidth, endY + halfHeight, other => {\n\t\t\tif(!overlapsAt(searcher, endX, endY, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// A sensor blocks nothing: a solid mover passes through it, left to the overlap callback.\n\t\t\tif(other.components.body[BODY_SENSOR_INDEX] !== 0) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Already inside it before the move began: it is not what this move ran into, and blocking on it would\n\t\t\t// pin the entity inside with no way out. Let the move through and leave the pair to the overlap callback.\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcandidates.push(other);\n\t\t});\n\n\t\treturn candidates;\n\t}\n\n\t// The expensive half: the move is known to stop short, and this finds where by halving between a `clear`\n\t// fraction and a `blocked` one. Contact is halved in on rather than solved - three shapes at any rotation\n\t// have no single first-touch formula - and the result is always taken from a position that tested clear.\n\tprivate refine(searcher: Searcher, candidates: Array<CollisionEntity<T>>, moveX: number, moveY: number, distance: number): SweepResult<T> {\n\t\tconst { x, y } = searcher;\n\t\tlet clear = 0;\n\t\tlet blocked = 1;\n\t\tconst tolerance = CONTACT_TOLERANCE / distance;\n\t\tfor(let i = 0; i < MAX_REFINEMENTS && blocked - clear > tolerance; i++) {\n\t\t\tconst middle = (clear + blocked) / 2;\n\t\t\tif(this.blockedAt(searcher, candidates, moveX, moveY, middle)) {\n\t\t\t\tblocked = middle;\n\t\t\t} else {\n\t\t\t\tclear = middle;\n\t\t\t}\n\t\t}\n\n\t\t// Whatever is in the way at the first blocked position: the entity rests a hair short of it.\n\t\tconst blocking: Array<CollisionEntity<T>> = [];\n\t\tconst blockedX = x + moveX * blocked;\n\t\tconst blockedY = y + moveY * blocked;\n\t\tfor(const other of candidates) {\n\t\t\tif(overlapsAt(searcher, blockedX, blockedY, other)) {\n\t\t\t\tblocking.push(other);\n\t\t\t}\n\t\t}\n\n\t\t// An entity already against something is left exactly where it is, rather than crawling a sub-tolerance\n\t\t// step forward every run and reporting a position change for no visible move.\n\t\treturn { fraction: clear * distance > CONTACT_TOLERANCE ? clear : 0, blocking };\n\t}\n\n\t// Whether `self` would be inside any of `candidates` that far along its move.\n\tprivate blockedAt(searcher: Searcher, candidates: Array<CollisionEntity<T>>, moveX: number, moveY: number, fraction: number): boolean {\n\t\tconst x = searcher.x + moveX * fraction;\n\t\tconst y = searcher.y + moveY * fraction;\n\t\tfor(const other of candidates) {\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Runs `handle` for every collidable entity in the box that `searcher` may collide with: the tree search and\n\t// the category masks, applied before any shape test so an impossible pair is settled by two ANDs.\n\tprivate forEachCandidate(searcher: Searcher, minX: number, minY: number, maxX: number, maxY: number, handle: (other: CollisionEntity<T>) => void): void {\n\t\tfor(const bucket of this.buckets) {\n\t\t\t// Half of canCollide, settled once per bucket: all entries share this category.\n\t\t\tif((searcher.mask & bucket.category) === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor(const found of bucket.index.search(minX, minY, maxX, maxY)) {\n\t\t\t\tconst other = bucket.entries[found];\n\t\t\t\tif(other.entityId === searcher.entityId) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// The other half of canCollide, per candidate: each entity carries its own mask.\n\t\t\t\tif((other.components.body[BODY_MASK_INDEX] & searcher.category) === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Already killed this run, or by another worker sharing this block.\n\t\t\t\tif(other.components.entity?.[DEAD_INDEX] === 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\thandle(other);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Merges what stopped each axis of a slide, without repeating an entity that stopped both. Either side empty is\n// the common case, so it is handed straight back rather than copied.\nfunction mergeBlocking<T extends PhysicsUpdateComponents>(a: Array<CollisionEntity<T>>, b: Array<CollisionEntity<T>>): Array<CollisionEntity<T>> {\n\tif(a.length === 0) {\n\t\treturn b;\n\t}\n\tif(b.length === 0) {\n\t\treturn a;\n\t}\n\n\tconst merged = [...a];\n\tfor(const other of b) {\n\t\tif(!merged.some(entry => entry.entityId === other.entityId)) {\n\t\t\tmerged.push(other);\n\t\t}\n\t}\n\n\treturn merged;\n}\n\n// Reads what a searching entity is measured by, or undefined for one that cannot collide.\nfunction toSearcher<T extends PhysicsUpdateComponents>(self: MovingEntity<T>): Searcher | undefined {\n\t// No body means no collisions, though the system can still move it - the move query does not require a body.\n\tconst body = self.components.body;\n\tif(!body) {\n\t\treturn undefined;\n\t}\n\n\t// Mask 0 runs into nothing, so no bucket is worth searching. This only stops it finding a collision; something\n\t// whose mask accepts it can still find this one, an asymmetry the symmetric canCollide rule then rules out.\n\tconst mask = body[BODY_MASK_INDEX];\n\tif(mask === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst transform = self.components.transform;\n\tconst shape = body[BODY_SHAPE_INDEX];\n\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\tif(shapeIsEmpty(shape, width, height)) {\n\t\treturn undefined;\n\t}\n\n\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\n\treturn {\n\t\tentityId: self.entityId,\n\t\tshape,\n\t\tx: transform[TRANSFORM_X_INDEX],\n\t\ty: transform[TRANSFORM_Y_INDEX],\n\t\twidth,\n\t\theight,\n\t\tangle,\n\t\thalfWidth: shapeHalfWidth(shape, width, height, angle),\n\t\thalfHeight: shapeHalfHeight(shape, width, height, angle),\n\t\tcategory: body[BODY_CATEGORY_INDEX],\n\t\tmask,\n\t\tsensor: body[BODY_SENSOR_INDEX] !== 0,\n\t};\n}\n\n// Whether the searcher, put down at (x, y) rather than its own block's position, overlaps `other`. Taking the\n// position as arguments lets a sweep try a whole path without writing any of it.\nfunction overlapsAt<T extends PhysicsUpdateComponents>(searcher: Searcher, x: number, y: number, other: CollisionEntity<T>): boolean {\n\tconst transform = other.components.transform;\n\n\treturn shapesOverlap(\n\t\tsearcher.shape, x, y, searcher.width, searcher.height, searcher.angle,\n\t\tother.components.body[BODY_SHAPE_INDEX],\n\t\ttransform[TRANSFORM_X_INDEX], transform[TRANSFORM_Y_INDEX],\n\t\ttransform[TRANSFORM_WIDTH_INDEX], transform[TRANSFORM_HEIGHT_INDEX], transform[TRANSFORM_ANGLE_INDEX],\n\t);\n}\n","import Flatbush from 'flatbush';\nimport type { EntityUpdateComponents } from '@daneren2005/shared-memory-ecs';\nimport { BODY_SHAPE_INDEX, SHAPE_RECTANGLE } from '../components/body-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { shapeHalfHeight, shapeHalfWidth } from '../math/shapes';\n\n// The blocks the index reads off each entity. Only the transform is needed; the body refines the box to the\n// entity's actual shape. Anything else a game queried travels along untouched for a filter to read.\nexport type SpatialComponents = {\n\ttransform: Float32Array\n\tbody?: Uint32Array\n};\n\n// One entity as the index hands it back: its id plus whatever blocks came with it.\nexport interface SpatialEntity<T extends SpatialComponents = SpatialComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// Which found entities are wanted. Run per candidate, so it is the place for what the index cannot know - the\n// searcher's own id, which team, whether it is dead - and a reject is as if it were never in the box.\nexport type SpatialFilter<T extends SpatialComponents = SpatialComponents> = (entity: SpatialEntity<T>) => boolean;\n\n// An R-tree over a set of entities, for questions about where things are rather than what is hitting what: who\n// is in this area, and what is nearest a point. The same machinery as the collision broadphase minus everything\n// collision specific, so answers are in terms of each entity's axis-aligned box and a `filter` picks the ones\n// that matter. It is a snapshot - nothing reads the blocks after the constructor - so build one per run.\n//\n// const index = new SpatialIndex(queries.collidable);\n// const nearest = index.findNearest(x, y, 150, other => other.entityId !== entityId);\nexport default class SpatialIndex<T extends SpatialComponents = SpatialComponents> {\n\tprivate entries: Array<SpatialEntity<T>> = [];\n\t// Left off for an empty index: Flatbush cannot hold zero items, and there is no answer to give anyway.\n\tprivate index: Flatbush | undefined;\n\n\t// `entities` is anything shaped like a query result, so a system's own `queries.whatever` goes straight in.\n\t// Entities without a transform are skipped - something with no place in the world cannot be found by a search\n\t// of it.\n\tconstructor(entities: Iterable<{ entityId: number, components: EntityUpdateComponents }>) {\n\t\t// Collected up front because Flatbush needs its item count before any are added, only known once everything\n\t\t// without a transform is dropped.\n\t\tconst bounds: Array<number> = [];\n\n\t\tfor(const entity of entities) {\n\t\t\t// The ECS types blocks generically as ComponentTypedArray; narrow to the concrete arrays once here.\n\t\t\tconst components = entity.components as T;\n\t\t\tconst transform = components.transform;\n\t\t\tif(!transform) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// No body means the transform's width and height are a plain rectangle, keeping the index usable for\n\t\t\t// anything with a place in the world, not only things that collide.\n\t\t\tconst body = components.body;\n\t\t\tconst shape = body ? body[BODY_SHAPE_INDEX] : SHAPE_RECTANGLE;\n\t\t\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\t\t\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\t\t\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\t\t\tconst x = transform[TRANSFORM_X_INDEX];\n\t\t\tconst y = transform[TRANSFORM_Y_INDEX];\n\n\t\t\t// Unlike the collision broadphase, a zero-area entity is kept: it still has a position to be found at.\n\t\t\tconst halfWidth = shapeHalfWidth(shape, width, height, angle);\n\t\t\tconst halfHeight = shapeHalfHeight(shape, width, height, angle);\n\n\t\t\tthis.entries.push({ entityId: entity.entityId, components });\n\t\t\tbounds.push(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight);\n\t\t}\n\n\t\tif(this.entries.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = new Flatbush(this.entries.length);\n\t\tfor(let i = 0; i < this.entries.length; i++) {\n\t\t\tindex.add(bounds[i * 4], bounds[i * 4 + 1], bounds[i * 4 + 2], bounds[i * 4 + 3]);\n\t\t}\n\t\tindex.finish();\n\n\t\tthis.index = index;\n\t}\n\n\t// How many entities are in the index, which is not how many were handed to it: transform-less ones are out.\n\tget size(): number {\n\t\treturn this.entries.length;\n\t}\n\n\t// Everything whose box reaches into the one given, in no particular order. Touching counts, so an exact\n\t// edge question is the caller's to settle on the results.\n\tsearch(minX: number, minY: number, maxX: number, maxY: number, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\tconst index = this.index;\n\t\tif(!index) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = this.entries;\n\t\t// Filtering inside the search keeps a reject from ever reaching the results array.\n\t\tconst found = filter ? index.search(minX, minY, maxX, maxY, i => filter(entries[i]))\n\t\t\t: index.search(minX, minY, maxX, maxY);\n\n\t\treturn found.map(i => entries[i]);\n\t}\n\n\t// `search` from the middle out: everything within `reachX` / `reachY` of (x, y), the form most searches take.\n\tsearchAround(x: number, y: number, reachX: number, reachY: number, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\treturn this.search(x - reachX, y - reachY, x + reachX, y + reachY, filter);\n\t}\n\n\t// The closest entity to (x, y) that `filter` accepts, or undefined when none is within `maxDistance`. A walk\n\t// of the tree in distance order rather than a search-then-sort, so it never measures the far side of the\n\t// world. Distance is to the entity's box, not its centre, so a big thing is as near as its nearest edge.\n\tfindNearest(x: number, y: number, maxDistance = Infinity, filter?: SpatialFilter<T>): SpatialEntity<T> | undefined {\n\t\treturn this.findNearby(x, y, 1, maxDistance, filter)[0];\n\t}\n\n\t// The `maxResults` closest entities to (x, y), nearest first, on the same terms as findNearest.\n\tfindNearby(x: number, y: number, maxResults: number, maxDistance = Infinity, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\tconst index = this.index;\n\t\tif(!index) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = this.entries;\n\t\t// Handed to Flatbush rather than applied afterwards, so `maxResults` counts wanted entities: filtering after\n\t\t// would let a few rejects use up the allowance and hide a valid target behind them.\n\t\tconst found = filter ? index.neighbors(x, y, maxResults, maxDistance, i => filter(entries[i]))\n\t\t\t: index.neighbors(x, y, maxResults, maxDistance);\n\n\t\treturn found.map(i => entries[i]);\n\t}\n}\n","import { BODY_SENSOR_INDEX, BODY_SHAPE_INDEX, SHAPE_RECTANGLE } from '../components/body-component';\nimport { BOUNCINESS_INDEX } from '../components/bounciness-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport { contactNormal } from '../math/shapes';\nimport type { Vector } from '../math/shapes';\n\ninterface BounceEntity {\n\tcomponents: {\n\t\ttransform: Float32Array\n\t\tvelocity?: Float32Array\n\t\tbounciness?: Float32Array\n\t\tbody?: Uint32Array\n\t}\n}\n\n// Scratch, reused rather than allocated: a run is one unbroken pass, never re-entered part way through.\nconst NORMAL: Vector = { x: 0, y: 0 };\n\n// Turns both sides of a contact around, each by its own bounciness. The two leave along the same line, so the\n// normal is worked out once and the far side is turned round rather than asked for again.\nexport function bouncePair(self: BounceEntity, other: BounceEntity): void {\n\tif(!canBounce(self) && !canBounce(other)) {\n\t\treturn;\n\t}\n\n\tif(isSurface(self, other) && collisionNormal(self, other, NORMAL)) {\n\t\treflect(self, NORMAL.x, NORMAL.y);\n\t\treflect(other, -NORMAL.x, -NORMAL.y);\n\t}\n}\n\n// Turns `self` around off `other` and writes the new heading into its velocity block. `bounciness` is how much\n// of the speed into the surface comes back: 1 reflects perfectly, 0 cancels it (slide along the surface) - see\n// BouncinessComponent. Only `self` is touched.\nexport function bounce(self: BounceEntity, other: BounceEntity): void {\n\tif(!canBounce(self) || !isSurface(self, other)) {\n\t\treturn;\n\t}\n\n\tif(collisionNormal(self, other, NORMAL)) {\n\t\treflect(self, NORMAL.x, NORMAL.y);\n\t}\n}\n\n// Mirrors an entity's velocity about a surface it is leaving along `normal`, a unit vector pointing away from it.\nfunction reflect(entity: BounceEntity, normalX: number, normalY: number): void {\n\tconst bounciness = entity.components.bounciness;\n\tconst velocity = entity.components.velocity;\n\tif(!bounciness || !velocity) {\n\t\treturn;\n\t}\n\n\tconst velocityX = velocity[VELOCITY_X_INDEX];\n\tconst velocityY = velocity[VELOCITY_Y_INDEX];\n\n\t// How much of the velocity points into what it hit. Zero or more means it is already on its way out, and\n\t// reflecting again while the two still overlap would turn it straight back in.\n\tconst into = velocityX * normalX + velocityY * normalY;\n\tif(into >= 0) {\n\t\treturn;\n\t}\n\n\t// Reverse the normal component and scale by bounciness, leaving the surface component alone. Scale is 2 at\n\t// bounciness 1 (mirror reflection) and 1 at 0 (removes the normal component, sliding along the surface).\n\tconst scale = (1 + bounciness[BOUNCINESS_INDEX]) * into;\n\tvelocity[VELOCITY_X_INDEX] = velocityX - scale * normalX;\n\tvelocity[VELOCITY_Y_INDEX] = velocityY - scale * normalY;\n}\n\nfunction canBounce(entity: BounceEntity): boolean {\n\treturn entity.components.bounciness !== undefined && entity.components.velocity !== undefined;\n}\n\n// A sensor is felt only through onCollision, never as a surface, so neither side bounces. The pair is still\n// reported - this decides the bounce, not the collision.\nfunction isSurface(self: BounceEntity, other: BounceEntity): boolean {\n\treturn !isSensorBody(self.components.body) && !isSensorBody(other.components.body);\n}\n\n// Which way `self` is pushed off `other`, as a unit vector, or false when the two are exactly on top of each\n// other and there is no such direction. Taken off the shapes themselves, at the angle they are turned to, so a\n// contact that is not square to the world still reflects the speed that was closing on it.\nfunction collisionNormal(self: BounceEntity, other: BounceEntity, out: Vector): boolean {\n\tconst selfTransform = self.components.transform;\n\tconst otherTransform = other.components.transform;\n\n\treturn contactNormal(\n\t\tshapeOf(self.components.body),\n\t\tselfTransform[TRANSFORM_X_INDEX], selfTransform[TRANSFORM_Y_INDEX],\n\t\tselfTransform[TRANSFORM_WIDTH_INDEX], selfTransform[TRANSFORM_HEIGHT_INDEX], selfTransform[TRANSFORM_ANGLE_INDEX],\n\t\tshapeOf(other.components.body),\n\t\totherTransform[TRANSFORM_X_INDEX], otherTransform[TRANSFORM_Y_INDEX],\n\t\totherTransform[TRANSFORM_WIDTH_INDEX], otherTransform[TRANSFORM_HEIGHT_INDEX], otherTransform[TRANSFORM_ANGLE_INDEX],\n\t\tout,\n\t);\n}\n\n// A size-only entity with no body collides as a rectangle.\nfunction shapeOf(body: Uint32Array | undefined): number {\n\treturn body ? body[BODY_SHAPE_INDEX] : SHAPE_RECTANGLE;\n}\n\n// A missing body is a solid, not a sensor.\nfunction isSensorBody(body: Uint32Array | undefined): boolean {\n\treturn body !== undefined && body[BODY_SENSOR_INDEX] !== 0;\n}\n\nexport default bounce;\n","import { addAtomicFloat32 } from '@daneren2005/shared-memory-objects/utils/atomic-math';\nimport { storeFloat32 } from '@daneren2005/shared-memory-objects/utils/float32-atomics';\nimport { DEAD_INDEX } from '@daneren2005/shared-memory-ecs';\nimport type { ComponentMap, ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction } from '@daneren2005/shared-memory-ecs';\nimport type { PhysicsComponents, PhysicsUpdateComponents } from '../components/registry';\nimport {\n\tINTERPOLATION_DURATION_INDEX,\n\tINTERPOLATION_PREV_X_INDEX,\n\tINTERPOLATION_PREV_Y_INDEX,\n\tINTERPOLATION_TICK_INDEX,\n} from '../components/interpolation-component';\nimport { TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport CollisionBroadphase, { COLLIDABLE_QUERY, type CollisionEntity, type CollisionFunction, type MovingEntity } from './collision';\nimport { bouncePair } from './bounce';\n\n// The per-run data object every physics update is handed: the base one plus the step counter the interpolation\n// component is stamped with. PhysicsSystem fills `tick` from addDataToWorld, so a subclass adding data must\n// call `super.addDataToWorld(world)` or nothing gets a tick and interpolation stops noticing new steps.\nexport interface PhysicsWorld extends ComponentSystemWorld {\n\t// Which physics step this is, bumped per run. Only compared for equality - a publication stamp, not a clock.\n\ttick: number\n\t// Whether to report this run's moves. Set by PhysicsSystem from whether anything is listening, since the cost\n\t// is paid in the worker (an id per moved entity, cloned across the boundary). Undefined means report.\n\treportMoves?: boolean\n}\n\n// The event a move is reported through, emitted on the system on the main thread once the run completes,\n// carrying the ids of everything that moved:\n//\n// physicsSystem.on(POSITION_UPDATED_EVENT, (entityIds: Array<number>) => { ... });\n//\n// Reported as one id array per run rather than per entity, since nearly everything moves nearly every run. No\n// position travels with the id: the transform is a SharedArrayBuffer block already on the main thread, so a\n// listener reads it off `entity.components.transform`.\nexport const POSITION_UPDATED_EVENT = 'position-updated';\n\n// What a game asks createPhysicsUpdate for on top of plain movement.\nexport interface PhysicsUpdateOptions<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> {\n\t// Run for each entity that moved into another, straight after its own move. Optional: an update with no\n\t// callback still sweeps and comes to rest against what is in the way, it just decides nothing about it.\n\tonCollision?: CollisionFunction<C, T, W>\n\t// Extra components to hand the update, and so the callback, beyond transform and velocity. List a health\n\t// component here to take damage on collision. Optional per entity: one without it is still moved and collided.\n\toptional?: Array<keyof C & string>\n}\n\n// What PhysicsSystem needs to know about an update to set up its components and queries. Stamped on by\n// createPhysicsUpdate so a game declares its collision setup once, in the module both backends import.\nexport interface PhysicsUpdateMetadata<C extends ComponentMap> {\n\t// Whether the update needs the collidable query. Always true for createPhysicsUpdate, which always sweeps;\n\t// the bare physicsUpdate carries no metadata and never asks for it.\n\tcollision: boolean\n\toptional: Array<keyof C & string>\n}\n\nexport type PhysicsUpdateFunction<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> = EntityUpdateFunction<C, T, W> & {\n\tphysics: PhysicsUpdateMetadata<C>\n};\n\n// Builds the update a game runs physics through when it wants its entities to notice each other. The result is\n// a plain EntityUpdateFunction that goes to both backends: createComponentWorker in the worker file, and\n// PhysicsSystem's `updateFunction` for the in-process fallback.\n//\n// export const gamePhysicsUpdate = createPhysicsUpdate<Components, GameUpdateComponents>({\n// optional: ['health'],\n// onCollision(world, a, b, queries, callbacks) { ... },\n// });\n//\n// Every update built here sweeps, so an entity comes to rest against what is in its way. The callback is on top\n// and optional; movement that walks through everything is the bare physicsUpdate below. The callback is baked\n// in rather than passed to PhysicsSystem because functions do not survive postMessage to the worker.\nexport function createPhysicsUpdate<\n\t// Constrained to a map holding the physics components because `T` is the game's components narrowed to\n\t// transform and velocity; every game already spreads physicsRegistry into its own, as PhysicsSystem requires.\n\tC extends ComponentMap & PhysicsComponents = PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n>(options: PhysicsUpdateOptions<C, T, W> = {}): PhysicsUpdateFunction<C, T, W> {\n\tconst { onCollision, optional = [] } = options;\n\n\t// Built by preRun, read by each entity update after it. Safe as a closure variable because a run is one\n\t// unbroken pass - preRun, then every entity - never re-entered part way through.\n\tlet broadphase: CollisionBroadphase<T> | undefined;\n\n\tconst update: PhysicsUpdateFunction<C, T, W> = Object.assign(\n\t\t(world: W, entityId: number, components: T, queries: EntityQueryComponents<C>, callbacks: ComponentSystemCallbacks<C>) => {\n\t\t\t// Killed earlier this run\n\t\t\tif(components.entity?.[DEAD_INDEX] === 1) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst self: MovingEntity<T> = { entityId, components };\n\t\t\tconst interpolation = components.interpolation;\n\t\t\tconst seconds = world.elapsedTime / 1000;\n\t\t\tconst moveX = components.velocity[VELOCITY_X_INDEX] * seconds;\n\t\t\tconst moveY = components.velocity[VELOCITY_Y_INDEX] * seconds;\n\n\t\t\t// Taken before anything is written, so the segment a renderer blends along runs from this step's start\n\t\t\t// to its end.\n\t\t\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\n\t\t\t// No tree means this update was called by hand without preRun: nothing to sweep against, plain move.\n\t\t\t// Bound locally so it stays narrowed inside the overlap callback below.\n\t\t\tconst tree = broadphase;\n\t\t\tif(!tree) {\n\t\t\t\tmove(entityId, components.transform, moveX, moveY, callbacks, world.reportMoves);\n\t\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Native bounce turns an entity around on contact without a game callback; the bounciness component\n\t\t\t// decides whether and how much. Read before the move because it also decides how the move resolves.\n\t\t\tconst bouncing = components.bounciness !== undefined;\n\n\t\t\t// Resolved off the blocks and only written once known, so an entity is never inside another. The move\n\t\t\t// resolves with or without a callback - coming to rest is what physics does about a collision,\n\t\t\t// onCollision is what the game does. A non-bouncing entity slides along a single axis off a corner; a\n\t\t\t// bouncing one does not, since it is about to turn around off the face it hit.\n\t\t\tconst moved = tree.resolveMove(self, moveX, moveY, !bouncing);\n\t\t\tmove(entityId, components.transform, moved.moveX, moved.moveY, callbacks, world.reportMoves);\n\t\t\t// Before any callback, so the stamp covers exactly the pair physics produced. A callback that writes\n\t\t\t// the transform is a game move, blended towards next frame like a teleport.\n\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\t// A non-bouncing entity still has to look, since what it ran into may bounce off it.\n\t\t\tif(!tree.hasBounciness && !onCollision) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handled after the entity is put down, so both bounce and callback act on where it ended up; the\n\t\t\t// flipped velocity is applied by the next run. A contact is resolved once per run, by whichever side\n\t\t\t// reached it first - claimContact is what makes the second side's turn a no-op.\n\t\t\ttree.forEachOverlapping(self, other => {\n\t\t\t\tif(tree.claimContact(entityId, other.entityId)) {\n\t\t\t\t\tresolveContact(world, self, other, queries, callbacks, onCollision);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Then what the move was stopped against, which the overlap check cannot find: the entity rests\n\t\t\t// touching it, not through it. The two lists never share an entry.\n\t\t\tfor(const other of moved.blocking) {\n\t\t\t\tif(tree.claimContact(entityId, other.entityId)) {\n\t\t\t\t\tresolveContact(world, self, other, queries, callbacks, onCollision);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tphysics: {\n\t\t\t\t// The sweep needs the collidable query even with no callback, so this is not `onCollision !== undefined`.\n\t\t\t\tcollision: true,\n\t\t\t\toptional,\n\t\t\t},\n\t\t},\n\t);\n\n\t// preRun sees every entity at once, so it is the only place the tree can be built from one consistent moment,\n\t// before any of this run's movement.\n\tupdate.preRun = (world, entities, queries) => {\n\t\tbroadphase = new CollisionBroadphase<T>(queries[COLLIDABLE_QUERY] ?? [], world.elapsedTime / 1000);\n\t};\n\n\treturn update;\n}\n\n// Everything a contact means, applied once for the pair: both sides turned around by their own bounciness, then\n// the one callback. `self` is whichever side reached the contact first, so a game acting on only one of the two\n// would leave the other untouched half the time - the callback has to decide for both.\nfunction resolveContact<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld,\n>(\n\tworld: W,\n\tself: MovingEntity<T>,\n\tother: CollisionEntity<T>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n\tonCollision?: CollisionFunction<C, T, W>,\n): void {\n\tbouncePair(self, other);\n\tif(onCollision) {\n\t\tonCollision(world, self, other, queries, callbacks);\n\t}\n}\n\n// Movement only, and the only way to get movement with no collision detection: walks an entity straight through\n// anything in its way, for something that has no business noticing the world (a particle, a camera). For\n// entities stopped by each other, build the update with createPhysicsUpdate above, which sweeps around this move.\nexport function physicsUpdate<\n\tC extends ComponentMap & PhysicsComponents = PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n>(world: W, entityId: number, components: T, queries: EntityQueryComponents<C>, callbacks: ComponentSystemCallbacks<C>): void {\n\t// elapsedTime is ms, velocity is units per second.\n\tconst seconds = world.elapsedTime / 1000;\n\tconst interpolation = components.interpolation;\n\n\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\tmove(\n\t\tentityId,\n\t\tcomponents.transform,\n\t\tcomponents.velocity[VELOCITY_X_INDEX] * seconds,\n\t\tcomponents.velocity[VELOCITY_Y_INDEX] * seconds,\n\t\tcallbacks,\n\t\tworld.reportMoves,\n\t);\n\tfinishInterpolationStep(interpolation, world.tick);\n}\n\n// The half of publishing a step that happens before the move: where the entity stands now becomes the `prev`\n// the next frame blends out of, plus how much simulated time the segment covers. The duration is published\n// because a run does not always cover one step - a late run takes two at once - and a renderer dividing by the\n// fixed step would draw that at double speed. Nothing is written without the component, keeping it opt-in.\nfunction startInterpolationStep(interpolation: Float32Array | undefined, transform: Float32Array, elapsedTime: number): void {\n\tif(!interpolation) {\n\t\treturn;\n\t}\n\n\tinterpolation[INTERPOLATION_PREV_X_INDEX] = transform[TRANSFORM_X_INDEX];\n\tinterpolation[INTERPOLATION_PREV_Y_INDEX] = transform[TRANSFORM_Y_INDEX];\n\tinterpolation[INTERPOLATION_DURATION_INDEX] = elapsedTime;\n}\n\n// The half after the move. Written unconditionally, so an entity pressed against a wall still publishes a step\n// (with `prev` where it is) rather than going quiet and leaving a renderer blending against stale data. The\n// tick goes last through storeFloat32 - a release store, so a reader that sees it also sees the matching `prev`\n// and transform. That is the whole publication protocol; see INTERPOLATION_TICK_INDEX.\nfunction finishInterpolationStep(interpolation: Float32Array | undefined, tick: number): void {\n\tif(!interpolation) {\n\t\treturn;\n\t}\n\n\tstoreFloat32(interpolation, INTERPOLATION_TICK_INDEX, tick);\n}\n\n// Applies an already-decided move and reports where the entity ended up. The one place the position is written,\n// reached only once the final position is known. Applied with addAtomicFloat32 rather than `+=` because another\n// thread may be adding to the same shared position at once and a plain read-modify-write would drop one; the\n// `max` cap is unwanted, so Infinity leaves a plain atomic add. A move that went nowhere is not reported, which\n// keeps a world of still entities quiet. See PhysicsSystem for `reportMoves`.\nfunction move(entityId: number, transform: Float32Array, moveX: number, moveY: number, callbacks: PositionCallbacks, reportMoves?: boolean): void {\n\tif(moveX === 0 && moveY === 0) {\n\t\treturn;\n\t}\n\n\tif(moveX !== 0) {\n\t\taddAtomicFloat32(transform, TRANSFORM_X_INDEX, moveX, Infinity);\n\t}\n\tif(moveY !== 0) {\n\t\taddAtomicFloat32(transform, TRANSFORM_Y_INDEX, moveY, Infinity);\n\t}\n\n\tif(reportMoves === false) {\n\t\treturn;\n\t}\n\n\t// Just the id: the main thread reads the position off the shared block, so no copy is worth sending.\n\tcallbacks.emitSystemEvent(POSITION_UPDATED_EVENT, entityId);\n}\n\n// The one callback move reaches for, narrowed to the event physics reports.\ninterface PositionCallbacks {\n\temitSystemEvent(event: typeof POSITION_UPDATED_EVENT, entityId: number): void\n}\n\nexport default physicsUpdate;\n","import { ComponentSystem } from '@daneren2005/shared-memory-ecs';\nimport type { BaseEntity, BaseWorld, ComponentDefinitionMap, ComponentMap, ComponentSystemQuery, EntityUpdateComponents, EntityUpdateFunction, SystemConfig } from '@daneren2005/shared-memory-ecs';\nimport type { PhysicsComponents, PhysicsUpdateComponents } from '../components/registry';\nimport physicsUpdate, { POSITION_UPDATED_EVENT, type PhysicsUpdateMetadata, type PhysicsWorld } from './physics-update';\nimport { COLLIDABLE_QUERY } from './collision';\n\n// Default physics step, in ms. 20Hz rather than every frame: cheaper for no loss of correctness, only\n// smoothness, which InterpolationSystem restores. `deltaBetweenRuns: 0` runs it every frame, which a game with\n// very fast entities and no interpolation wants - a longer step is a longer move, and a move longer than an\n// obstacle is thick sweeps clean through it.\nexport const DEFAULT_PHYSICS_STEP_MS = 50;\n\nexport interface PhysicsSystemConfig<\n\tC extends ComponentMap & PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> extends Partial<SystemConfig> {\n\t// Worker entry point that runs the update off the main thread. Bundlers need the `new Worker(new URL(...))`\n\t// call written out in the game's own source, so the game supplies the file:\n\t//\n\t// getWorker: () => new Worker(new URL('./physics.worker.ts', import.meta.url), { type: 'module' })\n\t//\n\t// Left off, the simulation runs in-process on the main thread instead.\n\tgetWorker?: () => Worker\n\t// Forces the in-process worker even with a getWorker. Defaults to true when no getWorker was given.\n\tforceMainThread?: boolean\n\t// The update to run, defaulting to plain movement that walks through anything in its way. Build one with\n\t// createPhysicsUpdate for collisions and pass the same function the worker file hands to createComponentWorker\n\t// so both backends match; it stamps `optional` and the collidable query on, so they need not be repeated here.\n\tupdateFunction?: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> }\n\t// Extra components to send with each moving entity, for an update built by hand. Overrides what the update\n\t// function declared.\n\toptional?: Array<keyof C & string>\n\t// Whether to gather the collidable query for the update's collision detection. Defaults to what the update\n\t// declared: on for createPhysicsUpdate, off for the bare physicsUpdate.\n\tcollision?: boolean\n\t// Whether each run reports what moved through POSITION_UPDATED_EVENT. Defaults to whether anything is\n\t// listening, checked per run. Worth a setting because the worker pays for it - an id per moved entity, cloned\n\t// back across the boundary - so force it `true` only for a listener this system cannot see.\n\treportMoves?: boolean\n\t// Narrows which entities this system moves to a subset, for spreading one simulation across several workers:\n\t// give each the same update but a shard filter (`entity => entity.eid % workers === n`). The collidable query\n\t// and any extra `queries` are gathered whole regardless, so every shard sweeps against the whole world.\n\tfilter?: (entity: BaseEntity<C>) => boolean\n\t// Extra queries sent to the worker alongside the collidable one, for an update that reads more of the world\n\t// than the entity it moves (e.g. flocking). Read by name off `queries`, gathered whole regardless of `filter`,\n\t// and merged with the collidable query rather than replacing it.\n\tqueries?: { [key: string]: ComponentSystemQuery<C> }\n}\n\n// Moves every entity with a transform and a velocity, and (when the update detects collisions) reports overlaps\n// to the game's callback. Generic over `C` the game's component map, `T` the blocks its update touches, and `W`\n// the per-run data object.\nexport default class PhysicsSystem<\n\tC extends ComponentMap & PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> extends ComponentSystem<C, T, W> {\n\t// Bumped per run and stamped onto every interpolated entity so a renderer can tell a blended position from one\n\t// physics just replaced. Only has to change; a Float32 holds integers to 2^24, over nine days at a 50ms step.\n\ttick = 0;\n\n\t// Undefined means \"whenever something is listening\", worked out per run by addDataToWorld.\n\treportMoves: boolean | undefined;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: PhysicsSystemConfig<C, T, W> = {}) {\n\t\tconst updateFunction: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> } = options.updateFunction ?? physicsUpdate;\n\t\tconst optional = options.optional ?? updateFunction.physics?.optional ?? [];\n\t\tconst collision = options.collision ?? updateFunction.physics?.collision ?? false;\n\t\t// A moving entity reads its own body (collide category/mask) and bounciness before it searches, so both\n\t\t// travel with movers too - but only on the collision path, or they are blocks per mover for nothing.\n\t\t// Interpolation goes along unconditionally, since the update must publish into it and nothing flags which\n\t\t// entities have one. All are free for entities that lack them: the ECS only sends a block it actually holds.\n\t\tconst extraOptional: Array<keyof C & string> = [];\n\t\tif(collision && !optional.includes('body')) {\n\t\t\textraOptional.push('body');\n\t\t}\n\t\tif(collision && !optional.includes('bounciness')) {\n\t\t\textraOptional.push('bounciness');\n\t\t}\n\t\tif(!optional.includes('interpolation')) {\n\t\t\textraOptional.push('interpolation');\n\t\t}\n\t\tif(collision) {\n\t\t\textraOptional.push('entity');\n\t\t}\n\t\tconst movingOptional = extraOptional.length ? [...extraOptional, ...optional] : optional;\n\n\t\t// Collision is between anything with a transform and a body, not only movers, so a ship can hit a station.\n\t\t// Gathered only when something will read it, since it ships a block per entity in the world. Merged with\n\t\t// the game's own `queries`, and neither is narrowed by the shard `filter`.\n\t\t// `bounciness` has to be on this side too, not just the movers: a contact is resolved once and turns both\n\t\t// sides around by their own bounciness, and the broadphase decides from these blocks whether anything in the\n\t\t// run can bounce at all. Left off, hasBounciness is never true and nothing ever bounces.\n\t\tconst collidableOptional = optional.includes('bounciness') ? optional : ['bounciness', ...optional];\n\t\tconst collidableQuery = collision ? {\n\t\t\t[COLLIDABLE_QUERY]: {\n\t\t\t\trequired: ['transform', 'body'] as Array<keyof C>,\n\t\t\t\toptional: ['velocity', 'entity', ...collidableOptional] as Array<keyof C>,\n\t\t\t},\n\t\t} : undefined;\n\t\tconst queries = collidableQuery || options.queries ? { ...collidableQuery, ...options.queries } : undefined;\n\n\t\tsuper(world, {\n\t\t\tname: options.name ?? 'PhysicsSystem',\n\t\t\tdeltaBetweenRuns: options.deltaBetweenRuns ?? DEFAULT_PHYSICS_STEP_MS,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// Movement needs both; an entity with only one is not moved, though it can still be collided with.\n\t\t\trequired: ['transform', 'velocity'],\n\t\t\toptional: movingOptional,\n\t\t\tfilter: options.filter,\n\t\t\tupdateFunction,\n\t\t\tqueries,\n\n\t\t\tforceMainThread: options.forceMainThread ?? !options.getWorker,\n\t\t\t// Only reached when a getWorker was supplied: without one forceMainThread defaults to true.\n\t\t\tgetWorker: options.getWorker ?? (() => {\n\t\t\t\tthrow new Error('PhysicsSystem cannot start a worker: no getWorker was supplied');\n\t\t\t}),\n\t\t});\n\n\t\tthis.reportMoves = options.reportMoves;\n\t}\n\n\t// Stamps the run with its step number and whether to report what moved. A subclass overriding this to attach\n\t// its own data must call `super.addDataToWorld(world)`, or `tick` stays undefined and every interpolated\n\t// entity looks to a renderer like it has never had a physics step.\n\taddDataToWorld(world: W): void {\n\t\tworld.tick = ++this.tick;\n\t\t// Asked per run so a game can attach and drop the listener freely.\n\t\tworld.reportMoves = this.reportMoves ?? this.listenerCount(POSITION_UPDATED_EVENT) > 0;\n\t}\n}\n","import { loadFloat32 } from '@daneren2005/shared-memory-objects/utils/float32-atomics';\nimport type { EntityUpdateFunction } from '@daneren2005/shared-memory-ecs';\nimport type { InterpolationComponents, InterpolationUpdateComponents } from '../components/registry';\nimport {\n\tINTERPOLATION_DURATION_INDEX,\n\tINTERPOLATION_PREV_X_INDEX,\n\tINTERPOLATION_PREV_Y_INDEX,\n\tINTERPOLATION_PROGRESS_INDEX,\n\tINTERPOLATION_SYNCED_TICK_INDEX,\n\tINTERPOLATION_TICK_INDEX,\n\tINTERPOLATION_X_INDEX,\n\tINTERPOLATION_Y_INDEX,\n} from '../components/interpolation-component';\nimport { TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\n\n// Walks the render position along the segment between where the entity was before the last physics step and\n// where it is after it: `render = prev + (current - prev) * progress`.\n//\n// Every position it writes is one the simulation actually produced, so an entity is drawn easing into a wall or\n// turning a corner because that is what happened - not guessed forward from velocity and snapped back. The cost\n// is one fixed step of latency, always, in exchange for never being wrong.\n//\n// Two rules cover the pacing: advance by `elapsedTime / duration` of the segment each frame, and restart on the\n// next step when it lands, dropping whatever was left of the old segment. None of it is read off the physics\n// system's accumulator, which resets when a run is posted, not when it lands - different moments on a worker\n// thread, and the source of a stutter whenever a run takes longer than a frame. Instead the segment's `duration`\n// is published with it, so a late run just banks more time and the next segment covers it; a slow run costs\n// latency and nothing else.\nexport const interpolationUpdate: EntityUpdateFunction<InterpolationComponents, InterpolationUpdateComponents> = (world, entityId, components) => {\n\tconst interpolation = components.interpolation;\n\tconst transform = components.transform;\n\n\t// The stamp is read on both sides of everything it publishes: physics may be moving this entity on another\n\t// thread, and a `prev` from after the move with a transform from before draws it walking backwards.\n\tconst before = loadFloat32(interpolation, INTERPOLATION_TICK_INDEX);\n\tconst prevX = interpolation[INTERPOLATION_PREV_X_INDEX];\n\tconst prevY = interpolation[INTERPOLATION_PREV_Y_INDEX];\n\tconst duration = interpolation[INTERPOLATION_DURATION_INDEX];\n\tconst x = transform[TRANSFORM_X_INDEX];\n\tconst y = transform[TRANSFORM_Y_INDEX];\n\tconst after = loadFloat32(interpolation, INTERPOLATION_TICK_INDEX);\n\n\t// Caught mid-step, so the two ends do not belong together. Leave the render position for this one frame\n\t// rather than drawing from a mismatched pair - a 16ms hold is invisible, a jump is not.\n\tif(before !== after) {\n\t\treturn;\n\t}\n\n\t// No segment to be part way along: an entity physics never moved, or one whose first step has not landed.\n\t// Progress is left finished, not zero, so the first real step starts from the right place - the rule below\n\t// takes a whole segment off whatever was banked, and taking it off zero would open with unpayable debt.\n\tif(duration <= 0) {\n\t\tinterpolation[INTERPOLATION_PROGRESS_INDEX] = 1;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t\tinterpolation[INTERPOLATION_X_INDEX] = x;\n\t\tinterpolation[INTERPOLATION_Y_INDEX] = y;\n\n\t\treturn;\n\t}\n\n\tlet progress = interpolation[INTERPOLATION_PROGRESS_INDEX];\n\tif(after !== interpolation[INTERPOLATION_SYNCED_TICK_INDEX]) {\n\t\t// A step landed: restart at its beginning, which is where the entity was just drawn anyway (the new\n\t\t// segment's `prev` is the old one's end). Whatever was left undrawn is dropped rather than carried -\n\t\t// carrying it could only be a deficit, which pins the entity at `prev` and never gets paid off.\n\t\tprogress = 0;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t}\n\n\t// Capped at the segment end, which absorbs a slow physics run: the entity waits at the newest real position\n\t// and the wait is dropped, not banked, so a consistently-late worker costs latency and nothing else.\n\tprogress += world.elapsedTime / duration;\n\tif(progress > 1) {\n\t\tprogress = 1;\n\t}\n\tinterpolation[INTERPOLATION_PROGRESS_INDEX] = progress;\n\n\tinterpolation[INTERPOLATION_X_INDEX] = prevX + (x - prevX) * progress;\n\tinterpolation[INTERPOLATION_Y_INDEX] = prevY + (y - prevY) * progress;\n};\n\nexport default interpolationUpdate;\n","import { ComponentSystem } from '@daneren2005/shared-memory-ecs';\nimport type { BaseWorld, ComponentDefinitionMap, ComponentMap, EntityUpdateComponents, SystemConfig } from '@daneren2005/shared-memory-ecs';\nimport type { InterpolationComponents, InterpolationUpdateComponents } from '../components/registry';\nimport interpolationUpdate from './interpolation-update';\n\nexport interface InterpolationSystemConfig extends Partial<SystemConfig> {\n\t// Defaulted the opposite way to PhysicsSystem - main thread unless asked otherwise - since a render position\n\t// read a frame after it was computed is a frame stale, the latency this system exists to cut.\n\tforceMainThread?: boolean\n\tgetWorker?: () => Worker\n}\n\n// Fills in a render position for every entity with an `interpolation` component, once per frame, by blending\n// between the two positions physics published either side of its last step.\n//\n// It is deliberately not wired to the physics system: everything it needs is in the block, published by\n// whichever run wrote it. Pacing off the physics system would pace off when a run was posted, which on a worker\n// is not when its results arrive; see interpolation-update.ts. So it takes no configuration and follows a step\n// the game retunes mid-flight. Ordering after the physics system is a preference worth one frame of latency;\n// pause and `timeScale` need no code, since BaseWorld#runUpdate already skips and scales for free.\nexport default class InterpolationSystem<\n\tC extends ComponentMap & InterpolationComponents,\n> extends ComponentSystem<C, InterpolationUpdateComponents & EntityUpdateComponents<C>> {\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: InterpolationSystemConfig = {}) {\n\t\tsuper(world, {\n\t\t\tname: options.name ?? 'InterpolationSystem',\n\t\t\t// Every frame, which is the whole point.\n\t\t\tdeltaBetweenRuns: 0,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// No velocity is read - the blend is between two positions the simulation produced - so a velocity-less\n\t\t\t// entity is in this system on the same terms as a ship.\n\t\t\trequired: ['transform', 'interpolation'],\n\t\t\tupdateFunction: interpolationUpdate,\n\n\t\t\tforceMainThread: options.forceMainThread ?? !options.getWorker,\n\t\t\tgetWorker: options.getWorker ?? (() => {\n\t\t\t\tthrow new Error('InterpolationSystem cannot start a worker: no getWorker was supplied');\n\t\t\t}),\n\t\t});\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/components/transform-component.ts","../src/components/velocity-component.ts","../src/components/body-component.ts","../src/components/bounciness-component.ts","../src/components/interpolation-component.ts","../src/components/registry.ts","../src/math/shapes.ts","../src/systems/collision.ts","../src/systems/spatial-index.ts","../src/systems/bounce.ts","../src/systems/physics-update.ts","../src/systems/physics-system.ts","../src/systems/interpolation-update.ts","../src/systems/interpolation-system.ts"],"sourcesContent":["import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// Where an entity is, how big it is, and which way it faces: a position, its box, and the box's rotation.\nexport interface TransformComponent {\n\tindex: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n}\n\n// The defining half of a transform, from the entity template: the size that gives an entity a transform at all\n// (a config with no size does not load one). `width`/`height` are the unrotated box and required, since a\n// zero-area box never overlaps anything. `angle` is radians counter-clockwise from +x, the game's to set. A\n// round entity may give a `radius` instead, loaded as a width and height of its diameter; both at once throws.\nexport interface TransformConfig {\n\twidth: number\n\theight: number\n\tangle?: number\n\tradius?: number\n}\n// The live runtime half: the physics system moves the position and can turn the box every run, so both round-trip\n// through `save`. Size comes back from the template. `x`/`y` are the centre of the box, not a corner; `angle` is\n// radians counter-clockwise from +x.\nexport interface TransformSerialization {\n\tx: number\n\ty: number\n\tangle: number\n}\n\n// Indexes into the backing Float32Array block, exported because the physics update reads the same offsets off\n// the raw block.\nexport const TRANSFORM_X_INDEX = 0;\nexport const TRANSFORM_Y_INDEX = 1;\nexport const TRANSFORM_WIDTH_INDEX = 2;\nexport const TRANSFORM_HEIGHT_INDEX = 3;\nexport const TRANSFORM_ANGLE_INDEX = 4;\nexport const TRANSFORM_SIZE = 5;\n\nexport const transformDefinition: ComponentDefinition<TransformComponent, Float32Array, TransformConfig, TransformSerialization> = {\n\ttype: Float32Array,\n\tsize: TRANSFORM_SIZE,\n\t// A size marks a config as having a transform. x/y are not listed: they come off a save, not the template.\n\tloadProperties: ['width', 'height', 'radius'],\n\tload(entity, memory, config) {\n\t\t// A radius is the same size said differently; turn it into width/height so nothing downstream cares. Both\n\t\t// at once cannot be honoured, so it throws rather than picking a winner.\n\t\tconst radius = config.radius;\n\t\tlet width = config.width;\n\t\tlet height = config.height;\n\t\tif(radius !== undefined) {\n\t\t\tif(width !== undefined || height !== undefined) {\n\t\t\t\tthrow new Error('A transform takes either a radius or a width and height, not both');\n\t\t\t}\n\n\t\t\twidth = height = radius * 2;\n\t\t}\n\n\t\tconst index = memory.create([config.x, config.y, width, height, config.angle ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget x() {\n\t\t\t\treturn block[TRANSFORM_X_INDEX];\n\t\t\t},\n\t\t\tset x(value: number) {\n\t\t\t\tblock[TRANSFORM_X_INDEX] = value;\n\t\t\t},\n\t\t\tget y() {\n\t\t\t\treturn block[TRANSFORM_Y_INDEX];\n\t\t\t},\n\t\t\tset y(value: number) {\n\t\t\t\tblock[TRANSFORM_Y_INDEX] = value;\n\t\t\t},\n\t\t\tget width() {\n\t\t\t\treturn block[TRANSFORM_WIDTH_INDEX];\n\t\t\t},\n\t\t\tset width(value: number) {\n\t\t\t\tblock[TRANSFORM_WIDTH_INDEX] = value;\n\t\t\t},\n\t\t\tget height() {\n\t\t\t\treturn block[TRANSFORM_HEIGHT_INDEX];\n\t\t\t},\n\t\t\tset height(value: number) {\n\t\t\t\tblock[TRANSFORM_HEIGHT_INDEX] = value;\n\t\t\t},\n\t\t\tget angle() {\n\t\t\t\treturn block[TRANSFORM_ANGLE_INDEX];\n\t\t\t},\n\t\t\tset angle(value: number) {\n\t\t\t\tblock[TRANSFORM_ANGLE_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\treturn {\n\t\t\tx: component.x,\n\t\t\ty: component.y,\n\t\t\tangle: component.angle,\n\t\t};\n\t},\n};\n\nexport default transformDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// How fast an entity moves, in world units per second per axis. An entity with a transform and a velocity is\n// moved by the physics system every run.\nexport interface VelocityComponent {\n\tindex: number\n\tvelocityX: number\n\tvelocityY: number\n}\n\n// Named per-axis, not `x`/`y`, because configs are flat and `x`/`y` belong to the transform. Either axis alone\n// gives an entity a velocity, so both are optional and a missing one starts at 0.\nexport interface VelocityConfig {\n\tvelocityX?: number\n\tvelocityY?: number\n}\n// Both spawn config and live state (a game's systems apply forces), so it round-trips through `save`.\nexport type VelocitySerialization = VelocityConfig;\n\n// Indexes into the backing Float32Array block, exported because the physics update reads the same offsets off\n// the raw block.\nexport const VELOCITY_X_INDEX = 0;\nexport const VELOCITY_Y_INDEX = 1;\nexport const VELOCITY_SIZE = 2;\n\nexport const velocityDefinition: ComponentDefinition<VelocityComponent, Float32Array, VelocityConfig, VelocitySerialization> = {\n\ttype: Float32Array,\n\tsize: VELOCITY_SIZE,\n\tloadProperties: ['velocityX', 'velocityY'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.velocityX ?? 0, config.velocityY ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget velocityX() {\n\t\t\t\treturn block[VELOCITY_X_INDEX];\n\t\t\t},\n\t\t\tset velocityX(value: number) {\n\t\t\t\tblock[VELOCITY_X_INDEX] = value;\n\t\t\t},\n\t\t\tget velocityY() {\n\t\t\t\treturn block[VELOCITY_Y_INDEX];\n\t\t\t},\n\t\t\tset velocityY(value: number) {\n\t\t\t\tblock[VELOCITY_Y_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n\tsave(component) {\n\t\treturn {\n\t\t\tvelocityX: component.velocityX,\n\t\t\tvelocityY: component.velocityY,\n\t\t};\n\t},\n};\n\nexport default velocityDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// What an entity collides as and what it collides with. A body is loaded for anything with a size, so the way\n// to opt out is `collideCategory: 0`, not leaving the component off. Categories are the game's to name; the\n// library only reads them as bits.\nexport interface BodyComponent {\n\tindex: number\n\tshape: number\n\tcollideCategory: number\n\tcollideMask: number\n\t// A sensor takes part in detection - a mover overlapping it still gets its onCollision - but stops nothing:\n\t// nothing is swept short against it or bounces off it. Only about the response; it obeys canCollide unchanged.\n\tsensor: boolean\n\t// When set, this body's own move is tested along its whole path this run rather than only where it ends.\n\tcontinuousCollisionDetection: boolean\n}\n\n// The shapes a body can be, all held in the same transform (position, width, height, angle):\n// SHAPE_RECTANGLE - a width x height box turned to `angle`\n// SHAPE_CIRCLE - a circle of diameter `width`, unaffected by `angle`\n// SHAPE_CAPSULE - `width` end to end and `height` thick, lying along the way it faces\n// See src/math/shapes.ts for the geometry.\nexport const SHAPE_RECTANGLE = 1;\nexport const SHAPE_CIRCLE = 2;\nexport const SHAPE_CAPSULE = 3;\n\nconst KNOWN_SHAPES = [SHAPE_RECTANGLE, SHAPE_CIRCLE, SHAPE_CAPSULE];\n\n// Default category: a single bit, so a world that sets no categories behaves as before they existed.\nexport const DEFAULT_COLLIDE_CATEGORY = 1;\n// Default mask: every bit, so a default body meets anything whose own mask allows it. Stored in a Uint32Array,\n// so 0xFFFFFFFF ANDs correctly against any category despite not fitting a signed int.\nexport const DEFAULT_COLLIDE_MASK = 0xFFFFFFFF;\n\n// Whether two bodies collide: each side's mask must accept the other's category. Symmetric by design, so the\n// outcome does not depend on which entity moved first; a one-sided response is done by checking `other` inside\n// onCollision. `collideCategory: 0` collides with nothing. `&` is signed, so compare against 0, not > 0.\nexport function canCollide(categoryA: number, maskA: number, categoryB: number, maskB: number): boolean {\n\treturn (maskA & categoryB) !== 0 && (maskB & categoryA) !== 0;\n}\n\n// A size loads a body, as it loads a transform: anything with a place in the world is collidable by default,\n// and the collide properties narrow that. All defining config from the entity template, so there is no `save`.\nexport interface BodyConfig {\n\t// Not read by the loader, only listed in `loadProperties`: they mark a config as having a place in the world.\n\twidth?: number\n\theight?: number\n\n\t// One of the SHAPE_ constants. Defaults to a circle for a `radius` config and a rectangle otherwise, so only\n\t// capsules name their shape.\n\tshape?: number\n\t// Not read here - the transform turns it into width/height - but a `radius` config decides the default shape.\n\tradius?: number\n\t// The bits this entity collides as, and the bits it collides with. See canCollide.\n\tcollideCategory?: number\n\tcollideMask?: number\n\t// Makes the body a sensor: found and reported, but never blocking or bounced off. Defaults to solid.\n\tsensor?: boolean\n\tcontinuousCollisionDetection?: boolean\n}\n\n// Indexes into the backing Uint32Array block, exported because the broadphase reads the same offsets off the\n// raw shared block. Shape, sensor and ccd share one flags word - never read directly, always through the\n// helpers below so the packing stays in one place.\nexport const BODY_FLAGS_INDEX = 0;\nexport const BODY_CATEGORY_INDEX = 1;\nexport const BODY_MASK_INDEX = 2;\nexport const BODY_SIZE = 3;\n\n// The flags word holds the shape in its low bits and the boolean body flags above them. Shapes are 1..3, so\n// two bits cover them; the sensor/ccd bits sit clear of that.\nexport const BODY_SHAPE_MASK = 0b11;\nexport const BODY_SENSOR_FLAG = 0b100;\nexport const BODY_CCD_FLAG = 0b1000;\n\n// Readers off a raw body block, exported so the broadphase, bounce and game systems unpack the flags the same way.\nexport function bodyShape(body: Uint32Array): number {\n\treturn body[BODY_FLAGS_INDEX] & BODY_SHAPE_MASK;\n}\nexport function isSensor(body: Uint32Array): boolean {\n\treturn (body[BODY_FLAGS_INDEX] & BODY_SENSOR_FLAG) !== 0;\n}\nexport function isContinuous(body: Uint32Array): boolean {\n\treturn (body[BODY_FLAGS_INDEX] & BODY_CCD_FLAG) !== 0;\n}\n\nexport const bodyDefinition: ComponentDefinition<BodyComponent, Uint32Array, BodyConfig> = {\n\ttype: Uint32Array,\n\tsize: BODY_SIZE,\n\tloadProperties: ['width', 'height', 'radius', 'shape', 'collideCategory', 'collideMask', 'sensor', 'continuousCollisionDetection'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([\n\t\t\ttoFlags(config),\n\t\t\tconfig.collideCategory ?? DEFAULT_COLLIDE_CATEGORY,\n\t\t\tconfig.collideMask ?? DEFAULT_COLLIDE_MASK,\n\t\t]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget shape() {\n\t\t\t\treturn block[BODY_FLAGS_INDEX] & BODY_SHAPE_MASK;\n\t\t\t},\n\t\t\tset shape(value: number) {\n\t\t\t\tblock[BODY_FLAGS_INDEX] = (block[BODY_FLAGS_INDEX] & ~BODY_SHAPE_MASK) | (value & BODY_SHAPE_MASK);\n\t\t\t},\n\t\t\tget collideCategory() {\n\t\t\t\treturn block[BODY_CATEGORY_INDEX];\n\t\t\t},\n\t\t\tset collideCategory(value: number) {\n\t\t\t\tblock[BODY_CATEGORY_INDEX] = value;\n\t\t\t},\n\t\t\tget collideMask() {\n\t\t\t\treturn block[BODY_MASK_INDEX];\n\t\t\t},\n\t\t\tset collideMask(value: number) {\n\t\t\t\tblock[BODY_MASK_INDEX] = value;\n\t\t\t},\n\t\t\tget sensor() {\n\t\t\t\treturn (block[BODY_FLAGS_INDEX] & BODY_SENSOR_FLAG) !== 0;\n\t\t\t},\n\t\t\tset sensor(value: boolean) {\n\t\t\t\tblock[BODY_FLAGS_INDEX] = value ? (block[BODY_FLAGS_INDEX] | BODY_SENSOR_FLAG) : (block[BODY_FLAGS_INDEX] & ~BODY_SENSOR_FLAG);\n\t\t\t},\n\t\t\tget continuousCollisionDetection() {\n\t\t\t\treturn (block[BODY_FLAGS_INDEX] & BODY_CCD_FLAG) !== 0;\n\t\t\t},\n\t\t\tset continuousCollisionDetection(value: boolean) {\n\t\t\t\tblock[BODY_FLAGS_INDEX] = value ? (block[BODY_FLAGS_INDEX] | BODY_CCD_FLAG) : (block[BODY_FLAGS_INDEX] & ~BODY_CCD_FLAG);\n\t\t\t},\n\t\t};\n\t},\n};\n\n// Packs the config's shape and boolean flags into the single flags word stored at BODY_FLAGS_INDEX.\nfunction toFlags(config: BodyConfig): number {\n\tlet flags = toShape(config);\n\tif(config.sensor) {\n\t\tflags |= BODY_SENSOR_FLAG;\n\t}\n\tif(config.continuousCollisionDetection) {\n\t\tflags |= BODY_CCD_FLAG;\n\t}\n\n\treturn flags;\n}\n\n// An unknown shape throws rather than defaulting: colliding with the wrong outline is harder to spot than a\n// failed load. A config that names no shape is read off its size - `radius` is a circle, anything else a rectangle.\nfunction toShape(config: BodyConfig): number {\n\tconst shape = config.shape;\n\tif(shape === undefined) {\n\t\treturn config.radius !== undefined ? SHAPE_CIRCLE : SHAPE_RECTANGLE;\n\t} else if(!KNOWN_SHAPES.includes(shape)) {\n\t\tthrow new Error(`Unknown body shape: ${shape}`);\n\t}\n\n\treturn shape;\n}\n\nexport default bodyDefinition;\n","import type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\n\n// How much speed an entity keeps when it bounces, as a fraction of the velocity that pointed into what it hit:\n// 1 is a perfect bounce, 0.5 loses half on each, 0 does not turn around at all. See the bounce in\n// createPhysicsUpdate. Only bouncing entities carry this; one without it is still stopped by what it runs into\n// (the sweep does that for everything), it just does not rebound.\nexport interface BouncinessComponent {\n\tindex: number\n\tbounciness: number\n}\n\n// A `bounciness` config loads the component. Defining config from the entity template, so there is no `save`.\nexport interface BouncinessConfig {\n\tbounciness?: number\n}\n\n// Index into the backing Float32Array block, exported because the bounce reads the same offset off the raw block.\nexport const BOUNCINESS_INDEX = 0;\nexport const BOUNCINESS_SIZE = 1;\n\nexport const bouncinessDefinition: ComponentDefinition<BouncinessComponent, Float32Array, BouncinessConfig> = {\n\ttype: Float32Array,\n\tsize: BOUNCINESS_SIZE,\n\tloadProperties: ['bounciness'],\n\tload(entity, memory, config) {\n\t\tconst index = memory.create([config.bounciness ?? 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget bounciness() {\n\t\t\t\treturn block[BOUNCINESS_INDEX];\n\t\t\t},\n\t\t\tset bounciness(value: number) {\n\t\t\t\tblock[BOUNCINESS_INDEX] = value;\n\t\t\t},\n\t\t};\n\t},\n};\n\nexport default bouncinessDefinition;\n","import { loadFloat32, storeFloat32 } from '@daneren2005/shared-memory-objects/utils/float32-atomics';\nimport type { ComponentDefinition } from '@daneren2005/shared-memory-ecs';\nimport type { TransformComponent } from './transform-component';\nimport type { VelocityComponent } from './velocity-component';\n\n// Where an entity should be drawn, which diverges from where it is once physics runs less often than the screen\n// refreshes: a 50ms step against a 16ms frame updates the transform one frame in three, so drawing straight off\n// it stutters. This block holds a position that changes every frame instead.\n//\n// Filled by InterpolationSystem by blending between the positions before and after the last physics step, so\n// every position here is one the simulation produced, at the cost of one step of latency. Nothing in the\n// simulation reads it back - collision, the sweep and the spatial index all use the transform - which also keeps\n// it safe under lockstep multiplayer, where different frame rates would otherwise read different numbers.\nexport interface InterpolationComponent {\n\tindex: number\n\t// The render position, rewritten every frame by InterpolationSystem. A game reads it, and writes it only\n\t// through `snapEntity` after a teleport.\n\tx: number\n\ty: number\n\t// Where the entity stood before the step being blended. Written by the physics update.\n\tprevX: number\n\tprevY: number\n\t// The publication protocol, normally written only by the physics update and read only by the interpolation update\n\tprogress: number\n\tsyncedTick: number\n\tduration: number\n\ttick: number\n}\n\n// `interpolate` gives an entity this component, opt-in rather than automatic because it is 20 bytes and a\n// per-frame visit for something a game may never draw. `x`/`y` come off the same config the transform loads\n// from, so a render position starts where the entity spawned.\nexport interface InterpolationConfig {\n\tinterpolate?: boolean\n\tx?: number\n\ty?: number\n}\n\n// Indexes into the backing Float32Array block, exported because the physics and interpolation updates read\n// these offsets off the raw shared block.\nexport const INTERPOLATION_X_INDEX = 0;\nexport const INTERPOLATION_Y_INDEX = 1;\nexport const INTERPOLATION_PREV_X_INDEX = 2;\nexport const INTERPOLATION_PREV_Y_INDEX = 3;\n// How far along the current segment has been drawn: 0 at `prev`, 1 at the transform. A fraction, not\n// milliseconds, so leftover carries onto the next segment without remembering the old segment's length (segments\n// vary - see INTERPOLATION_DURATION_INDEX). Written only by the interpolation system.\nexport const INTERPOLATION_PROGRESS_INDEX = 4;\n// The step the interpolation system last reconciled against, to tell an already-started step from a new one.\nexport const INTERPOLATION_SYNCED_TICK_INDEX = 5;\n// How much simulated time this segment covers, in ms - the run's `elapsedTime`. Nearly always the step, but not\n// assumed to be: a late run banks more than a step and the next covers two, and dividing by the fixed step\n// would draw that at double speed. Written by the physics update.\nexport const INTERPOLATION_DURATION_INDEX = 6;\n// The physics step this block's `prev` belongs to, written last with a release store. A publication stamp: a\n// reader seeing the same tick on both sides of its own reads knows the `prev` and transform belong together,\n// and would otherwise draw the entity moving backwards. Also the arrival signal, which is why pacing lives here\n// rather than in the physics accumulator: that resets when a run is posted, this changes when it lands.\nexport const INTERPOLATION_TICK_INDEX = 7;\nexport const INTERPOLATION_SIZE = 8;\n\nexport const interpolationDefinition: ComponentDefinition<InterpolationComponent, Float32Array, InterpolationConfig> = {\n\ttype: Float32Array,\n\tsize: INTERPOLATION_SIZE,\n\tloadProperties: ['interpolate'],\n\tload(entity, memory, config) {\n\t\tconst x = config.x ?? 0;\n\t\tconst y = config.y ?? 0;\n\n\t\t// Spawn position on both sides of the blend, pacing at rest, so an entity added between steps is drawn\n\t\t// standing still. PhysicsSystem's first run stamps tick 1, so the 0 here reads as a new step to reconcile.\n\t\t// Duration 0 means \"no segment yet\", drawing the entity at its transform - as for anything never moved.\n\t\tconst index = memory.create([x, y, x, y, 0, 0, 0, 0]);\n\t\tconst block = memory.getBlock(index);\n\n\t\treturn {\n\t\t\tindex,\n\t\t\tget x() {\n\t\t\t\treturn block[INTERPOLATION_X_INDEX];\n\t\t\t},\n\t\t\tset x(value: number) {\n\t\t\t\tblock[INTERPOLATION_X_INDEX] = value;\n\t\t\t},\n\t\t\tget y() {\n\t\t\t\treturn block[INTERPOLATION_Y_INDEX];\n\t\t\t},\n\t\t\tset y(value: number) {\n\t\t\t\tblock[INTERPOLATION_Y_INDEX] = value;\n\t\t\t},\n\t\t\tget prevX() {\n\t\t\t\treturn block[INTERPOLATION_PREV_X_INDEX];\n\t\t\t},\n\t\t\tset prevX(value: number) {\n\t\t\t\tblock[INTERPOLATION_PREV_X_INDEX] = value;\n\t\t\t},\n\t\t\tget prevY() {\n\t\t\t\treturn block[INTERPOLATION_PREV_Y_INDEX];\n\t\t\t},\n\t\t\tset prevY(value: number) {\n\t\t\t\tblock[INTERPOLATION_PREV_Y_INDEX] = value;\n\t\t\t},\n\t\t\tget progress() {\n\t\t\t\treturn block[INTERPOLATION_PROGRESS_INDEX];\n\t\t\t},\n\t\t\tset progress(value: number) {\n\t\t\t\tblock[INTERPOLATION_PROGRESS_INDEX] = value;\n\t\t\t},\n\t\t\tget syncedTick() {\n\t\t\t\treturn block[INTERPOLATION_SYNCED_TICK_INDEX];\n\t\t\t},\n\t\t\tset syncedTick(value: number) {\n\t\t\t\tblock[INTERPOLATION_SYNCED_TICK_INDEX] = value;\n\t\t\t},\n\t\t\tget duration() {\n\t\t\t\treturn block[INTERPOLATION_DURATION_INDEX];\n\t\t\t},\n\t\t\tset duration(value: number) {\n\t\t\t\tblock[INTERPOLATION_DURATION_INDEX] = value;\n\t\t\t},\n\t\t\t// The stamp everything else is published under, so it is read and written atomically like the physics\n\t\t\t// update does: a release store paired with the acquire load the interpolation update reads it through.\n\t\t\tget tick() {\n\t\t\t\treturn loadFloat32(block, INTERPOLATION_TICK_INDEX);\n\t\t\t},\n\t\t\tset tick(value: number) {\n\t\t\t\tstoreFloat32(block, INTERPOLATION_TICK_INDEX, value);\n\t\t\t},\n\t\t};\n\t},\n\t// No `save`: everything here is derived from the transform and rebuilt on the next step, and `interpolate`\n\t// is defining config from the entity template.\n};\n\n// Anything with the two components this reads, written structurally so it does not need the game's component map.\nexport interface SnappableEntity {\n\tcomponents: {\n\t\ttransform?: TransformComponent\n\t\tinterpolation?: InterpolationComponent\n\t}\n}\n\n// Drops the segment an entity is part way along and puts drawn and simulated position at the same place. Call\n// after writing a transform directly: a teleport by assigning `transform.x` otherwise reads as a long move and\n// is drawn sliding the whole way, since a long move and a teleport are the same two numbers. Not needed for\n// anything physics did - those are steps the simulation took and are drawn as they happened.\nexport function snapEntity(entity: SnappableEntity): void {\n\tconst { transform, interpolation } = entity.components;\n\tif(!transform || !interpolation) {\n\t\treturn;\n\t}\n\n\tinterpolation.prevX = interpolation.x = transform.x;\n\tinterpolation.prevY = interpolation.y = transform.y;\n}\n\n// Anything `startSpawnInterpolation` reads: a transform and velocity to build the segment from, and the\n// interpolation block to write it into. Structural so it does not need the game's component map.\nexport interface SpawnableEntity {\n\tcomponents: {\n\t\ttransform?: TransformComponent\n\t\tvelocity?: VelocityComponent\n\t\tinterpolation?: InterpolationComponent\n\t}\n}\n\nexport interface SpawnInterpolationOptions {\n\t// The physics step, in ms. The first real run will land a step's worth of one of these after the *last* step,\n\t// i.e. `(1 - stepFraction)` of one from now - which is how long the seeded segment covers and how far along its\n\t// velocity the transform is jumped.\n\tstepMs: number\n\t// How far into the current step the spawn landed, 0..1 (`accumulator / stepMs`). What is left of the step\n\t// (`1 - stepFraction`) is the time until the first real run lands, so it sets both the seeded segment's length\n\t// and the forward jump. Clamped, so a value read a frame stale or on the step boundary still lands in range.\n\tstepFraction: number\n\t// The last physics tick, written as the segment's stamp so the first real step - stamped a later tick - reads\n\t// as a new one and reconciles. Getting it wrong only ever costs a one-frame hold, never a wrong position.\n\ttick: number\n}\n\n// Seeds a just-spawned, already-moving entity so a renderer draws it leaving its spawn point from the next frame,\n// instead of standing there until the first physics step reaches it - up to a full step of stillness that reads as\n// lag on a bullet fired with velocity. The render opens *at* the spawn point and moves out along the entity's own\n// velocity at its true speed; the transform is jumped forward to where that motion reaches by the time the first\n// real step lands, so the seeded segment hands straight over to it with no seam and the render is never drawn\n// ahead of the simulation. How far that is - the jump distance and the segment's length - is whatever is left of\n// the current step (`1 - stepFraction`), since that is when the first run lands.\n//\n// The jump is a real move of the transform, applied WITHOUT a collision sweep: the entity skips forward up to a\n// step, so it can tunnel through anything within that distance of the spawn, and it runs that far ahead of where\n// its velocity would otherwise have put it for the rest of its life. This is a deliberate trade for spawned\n// projectiles, where leaving the muzzle instantly matters more than the first step's sweep: a shot fired into a\n// target still lands inside it and is caught and killed by the next run's overlap test; only something thin enough\n// to sit entirely within that jumped span is passed through uncaught. Do not use it for something that must not\n// skip a step of collision. A no-op without all of transform, velocity and interpolation.\n// `PhysicsSystem#startInterpolation` fills the options in from its own step and accumulator; call this directly\n// only when driving physics by hand. See `snapEntity` for the teleport counterpart.\nexport function startSpawnInterpolation(entity: SpawnableEntity, options: SpawnInterpolationOptions): void {\n\tconst { transform, velocity, interpolation } = entity.components;\n\tif(!transform || !velocity || !interpolation) {\n\t\treturn;\n\t}\n\n\tconst fraction = options.stepFraction < 0 ? 0 : options.stepFraction > 1 ? 1 : options.stepFraction;\n\t// What is left of the current step: the time until the first real run lands, so both how long the seeded\n\t// segment lasts and how far the transform is jumped forward.\n\tconst remaining = 1 - fraction;\n\tconst seconds = options.stepMs / 1000;\n\tconst spawnX = transform.x;\n\tconst spawnY = transform.y;\n\tconst jumpX = velocity.velocityX * seconds * remaining;\n\tconst jumpY = velocity.velocityY * seconds * remaining;\n\n\t// The segment runs from where it spawned to the jump target, and the transform is jumped to that target so the\n\t// render never runs ahead of the simulation. The render opens at the spawn point (progress 0) and moves out.\n\tinterpolation.prevX = spawnX;\n\tinterpolation.prevY = spawnY;\n\ttransform.x = spawnX + jumpX;\n\ttransform.y = spawnY + jumpY;\n\tinterpolation.x = spawnX;\n\tinterpolation.y = spawnY;\n\tinterpolation.duration = options.stepMs * remaining;\n\tinterpolation.progress = 0;\n\tinterpolation.syncedTick = options.tick;\n\t// Last, through the release store, so a reader that sees this stamp also sees the prev and progress above.\n\tinterpolation.tick = options.tick;\n}\n\nexport default interpolationDefinition;\n","import bodyDefinition, { type BodyComponent } from './body-component';\nimport bouncinessDefinition, { type BouncinessComponent } from './bounciness-component';\nimport interpolationDefinition, { type InterpolationComponent } from './interpolation-component';\nimport transformDefinition, { type TransformComponent } from './transform-component';\nimport velocityDefinition, { type VelocityComponent } from './velocity-component';\n\n// The component definitions this library supplies. A game spreads them into its own registry:\n//\n// const registry = { ...physicsRegistry, health: healthDefinition };\n//\n// The keys here are what the physics systems query on, so they must stay as the registry keys.\nexport const physicsRegistry = {\n\ttransform: transformDefinition,\n\tvelocity: velocityDefinition,\n\tbody: bodyDefinition,\n\tbounciness: bouncinessDefinition,\n\tinterpolation: interpolationDefinition,\n};\n\n// The slice of a game's component map the physics systems need. A type alias, not an interface, so it satisfies\n// the ECS's `ComponentMap` index signature; systems only require the game's map extends this.\nexport type PhysicsComponents = {\n\ttransform: TransformComponent\n\tvelocity: VelocityComponent\n\tbody: BodyComponent\n\tbounciness: BouncinessComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The strictly smaller slice InterpolationSystem needs: it never looks at velocity or body. Split out so the\n// interpolation update declares exactly what it touches.\nexport type InterpolationComponents = {\n\ttransform: TransformComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The same components as their backing arrays, the form an update sees on the worker thread. `body`, `bounciness`\n// and `interpolation` are optional because an entity may lack them; the collidable query requires `body`, so\n// CollisionComponents narrows it back to guaranteed there. A game widens this for extra callback components:\n//\n// type GameUpdateComponents = PhysicsUpdateComponents & { health?: Float32Array };\nexport type PhysicsUpdateComponents = {\n\ttransform: Float32Array\n\tvelocity: Float32Array\n\tbody?: Uint32Array\n\tbounciness?: Float32Array\n\tinterpolation?: Float32Array\n\t// For checking if entity is dead during run\n\tentity?: Uint32Array\n};\n\n// The blocks the interpolation update works on. Both required - the query asks for both - so no per-entity guard.\nexport type InterpolationUpdateComponents = {\n\ttransform: Float32Array\n\tinterpolation: Float32Array\n};\n","// Pure geometry for the shapes a transform plus a body describe, centred on (x, y) and turned `angle` radians\n// counter-clockwise. Loose number arguments rather than objects so the collision sweep can call per pair\n// without allocating.\n//\n// The three shapes are one idea: an oriented core grown by a radius.\n// rectangle - a width x height box, radius 0\n// circle - a point, radius width / 2\n// capsule - a segment along its facing, radius height / 2\n// A circle is a capsule whose segment has no length, so there are only two core kinds - box and segment - and\n// three pair tests rather than one per shape combination. See shapesOverlap.\n\nimport { SHAPE_CAPSULE, SHAPE_CIRCLE, SHAPE_RECTANGLE } from '../components/body-component';\n\nexport interface Vector {\n\tx: number\n\ty: number\n}\n\n// Scratch, reused rather than allocated: every user is a single unbroken pass, never re-entered part way through.\nconst GAP: Vector = { x: 0, y: 0 };\nconst CANDIDATE: Vector = { x: 0, y: 0 };\n\n// Clamped at 0 so a capsule shorter than it is wide collapses to a circle rather than turning inside out.\nexport function capsuleHalfLength(width: number, height: number): number {\n\treturn Math.max(0, (width - height) / 2);\n}\n\nexport function shapeRadius(shape: number, width: number, height: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn height / 2;\n\t}\n\n\treturn 0;\n}\n\n// A shape with no area never overlaps anything, which keeps a sizeless entity out of collisions. Which size\n// matters is per shape: a circle is nothing without its width, a capsule with no thickness is a bare line.\nexport function shapeIsEmpty(shape: number, width: number, height: number): boolean {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width <= 0;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn height <= 0;\n\t}\n\n\treturn width <= 0 || height <= 0;\n}\n\n// Half the axis-aligned box that fully contains a shape at any rotation - what the broadphase indexes it under,\n// so it must hold the whole shape. Asked per shape because a circle is its width alone: one whose height was\n// hand-written smaller is taller than the width/height rectangle a capsule or radius-loaded circle fits in.\nexport function shapeHalfWidth(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn Math.abs(Math.cos(angle)) * capsuleHalfLength(width, height) + height / 2;\n\t}\n\n\treturn boundsHalfWidth(width, height, angle);\n}\nexport function shapeHalfHeight(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\treturn width / 2;\n\t} else if(shape === SHAPE_CAPSULE) {\n\t\treturn Math.abs(Math.sin(angle)) * capsuleHalfLength(width, height) + height / 2;\n\t}\n\n\treturn boundsHalfHeight(width, height, angle);\n}\n\n// Whether two shapes of any kind overlap. Every pair is one of three cases: box+box (separating axis),\n// round+round (segment gap under the summed radii), or box+round (segment gap under the one radius). Touching\n// exactly counts as not overlapping throughout - the comparisons are strict.\nexport function shapesOverlap(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n): boolean {\n\tif(shapeIsEmpty(aShape, aWidth, aHeight) || shapeIsEmpty(bShape, bWidth, bHeight)) {\n\t\treturn false;\n\t}\n\n\tconst aIsRound = aShape !== SHAPE_RECTANGLE;\n\tconst bIsRound = bShape !== SHAPE_RECTANGLE;\n\n\tif(!aIsRound && !bIsRound) {\n\t\treturn orientedBoxesOverlap(aX, aY, aWidth, aHeight, aAngle, bX, bY, bWidth, bHeight, bAngle);\n\t} else if(aIsRound && !bIsRound) {\n\t\t// The mixed case is written once with the box first and reached from the other side by swapping.\n\t\treturn shapesOverlap(\n\t\t\tbShape, bX, bY, bWidth, bHeight, bAngle,\n\t\t\taShape, aX, aY, aWidth, aHeight, aAngle,\n\t\t);\n\t}\n\n\tconst bHalf = coreHalfLength(bShape, bWidth, bHeight);\n\tconst bCos = Math.cos(bAngle) * bHalf;\n\tconst bSin = Math.sin(bAngle) * bHalf;\n\tconst bRadius = shapeRadius(bShape, bWidth, bHeight);\n\n\tif(!aIsRound) {\n\t\treturn segmentBoxDistanceSquared(\n\t\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t\t\taX, aY, aWidth, aHeight, aAngle,\n\t\t) < bRadius * bRadius;\n\t}\n\n\tconst aHalf = coreHalfLength(aShape, aWidth, aHeight);\n\tconst aCos = Math.cos(aAngle) * aHalf;\n\tconst aSin = Math.sin(aAngle) * aHalf;\n\tconst reach = shapeRadius(aShape, aWidth, aHeight) + bRadius;\n\n\treturn segmentSegmentDistanceSquared(\n\t\taX - aCos, aY - aSin, aX + aCos, aY + aSin,\n\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t) < reach * reach;\n}\n\n// How far a shape's core reaches from its centre: a capsule's segment, and nothing for a circle or rectangle.\nfunction coreHalfLength(shape: number, width: number, height: number): number {\n\treturn shape === SHAPE_CAPSULE ? capsuleHalfLength(width, height) : 0;\n}\n\n// The unit direction a is pushed off b at their contact, written into `out`; false when the two are on top of\n// each other and there is no such direction. Split the same three ways as shapesOverlap and off the same\n// geometry, so the normal is the face that was actually hit rather than a guess from the bounding boxes: a pair\n// meeting at an angle reflects along the contact, not along whichever world axis happens to be nearest.\nexport function contactNormal(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aIsRound = aShape !== SHAPE_RECTANGLE;\n\tconst bIsRound = bShape !== SHAPE_RECTANGLE;\n\n\tif(!aIsRound && !bIsRound) {\n\t\treturn boxesContactNormal(aX, aY, aWidth, aHeight, aAngle, bX, bY, bWidth, bHeight, bAngle, out);\n\t} else if(aIsRound && bIsRound) {\n\t\treturn roundsContactNormal(aShape, aX, aY, aWidth, aHeight, aAngle, bShape, bX, bY, bWidth, bHeight, bAngle, out);\n\t} else if(aIsRound) {\n\t\treturn boxRoundContactNormal(bX, bY, bWidth, bHeight, bAngle, aShape, aX, aY, aWidth, aHeight, aAngle, out);\n\t}\n\n\t// The mixed case is written once with the box second and reached from the other side by turning it round.\n\tif(!boxRoundContactNormal(aX, aY, aWidth, aHeight, aAngle, bShape, bX, bY, bWidth, bHeight, bAngle, out)) {\n\t\treturn false;\n\t}\n\n\tout.x = -out.x;\n\tout.y = -out.y;\n\n\treturn true;\n}\n\n// Two boxes part along whichever of their four side directions they run into each other least far - the face\n// that was hit. The same axes and reaches the separating-axis test uses, kept as depths rather than a verdict.\nfunction boxesContactNormal(\n\taX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aCos = Math.cos(aAngle);\n\tconst aSin = Math.sin(aAngle);\n\tconst bCos = Math.cos(bAngle);\n\tconst bSin = Math.sin(bAngle);\n\tconst aHalfWidth = aWidth / 2;\n\tconst aHalfHeight = aHeight / 2;\n\tconst bHalfWidth = bWidth / 2;\n\tconst bHalfHeight = bHeight / 2;\n\tconst offsetX = aX - bX;\n\tconst offsetY = aY - bY;\n\n\tlet best = boxOverlapAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tout.x = aCos;\n\tout.y = aSin;\n\n\tlet depth = boxOverlapAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = -aSin;\n\t\tout.y = aCos;\n\t}\n\n\tdepth = boxOverlapAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = bCos;\n\t\tout.y = bSin;\n\t}\n\n\tdepth = boxOverlapAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n\tif(depth < best) {\n\t\tout.x = -bSin;\n\t\tout.y = bCos;\n\t}\n\n\tpointAwayFrom(offsetX, offsetY, out);\n\n\treturn true;\n}\n\n// Two rounds meet where their cores come closest - the line between those points, grown by nothing since both\n// radii lie along it. Two circles reduce to the line between their centres.\nfunction roundsContactNormal(\n\taShape: number, aX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbShape: number, bX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n\tout: Vector,\n): boolean {\n\tconst aHalf = coreHalfLength(aShape, aWidth, aHeight);\n\tconst aCos = Math.cos(aAngle) * aHalf;\n\tconst aSin = Math.sin(aAngle) * aHalf;\n\tconst bHalf = coreHalfLength(bShape, bWidth, bHeight);\n\tconst bCos = Math.cos(bAngle) * bHalf;\n\tconst bSin = Math.sin(bAngle) * bHalf;\n\n\tconst distance = Math.sqrt(segmentSegmentDistanceSquared(\n\t\taX - aCos, aY - aSin, aX + aCos, aY + aSin,\n\t\tbX - bCos, bY - bSin, bX + bCos, bY + bSin,\n\t\tGAP,\n\t));\n\tif(distance > 0) {\n\t\tout.x = GAP.x / distance;\n\t\tout.y = GAP.y / distance;\n\n\t\treturn true;\n\t}\n\n\t// The cores cross, so they have no gap to part along; the centre line is the best left.\n\treturn normalize(aX - bX, aY - bY, out);\n}\n\n// A round shape leaves a box along the gap between its core and the box - the box's face for a flat hit, its\n// corner for one off the end - which is the direction the round side is pushed. Not the box: the caller turns\n// it round for that.\nfunction boxRoundContactNormal(\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\n\troundShape: number, roundX: number, roundY: number, roundWidth: number, roundHeight: number, roundAngle: number,\n\tout: Vector,\n): boolean {\n\tconst half = coreHalfLength(roundShape, roundWidth, roundHeight);\n\tconst halfX = Math.cos(roundAngle) * half;\n\tconst halfY = Math.sin(roundAngle) * half;\n\n\tconst distance = Math.sqrt(segmentBoxDistanceSquared(\n\t\troundX - halfX, roundY - halfY, roundX + halfX, roundY + halfY,\n\t\tboxX, boxY, boxWidth, boxHeight, boxAngle,\n\t\tGAP,\n\t));\n\tif(distance > 0) {\n\t\tout.x = GAP.x / distance;\n\t\tout.y = GAP.y / distance;\n\n\t\treturn true;\n\t}\n\n\t// The core runs through the box, leaving no gap to point along. Fall back to the shallowest way out, in the\n\t// box's frame where it is axis aligned: its own two sides, or square across the core.\n\tconst cos = Math.cos(boxAngle);\n\tconst sin = Math.sin(boxAngle);\n\tconst offsetX = roundX - boxX;\n\tconst offsetY = roundY - boxY;\n\tconst localX = offsetX * cos + offsetY * sin;\n\tconst localY = -offsetX * sin + offsetY * cos;\n\tconst localHalfX = halfX * cos + halfY * sin;\n\tconst localHalfY = -halfX * sin + halfY * cos;\n\tconst radius = shapeRadius(roundShape, roundWidth, roundHeight);\n\tconst halfWidth = boxWidth / 2;\n\tconst halfHeight = boxHeight / 2;\n\n\tlet best = roundOverlapAlong(1, 0, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\tout.x = 1;\n\tout.y = 0;\n\n\tlet depth = roundOverlapAlong(0, 1, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\tif(depth < best) {\n\t\tbest = depth;\n\t\tout.x = 0;\n\t\tout.y = 1;\n\t}\n\n\tif(normalize(-localHalfY, localHalfX, CANDIDATE)) {\n\t\tdepth = roundOverlapAlong(CANDIDATE.x, CANDIDATE.y, localX, localY, localHalfX, localHalfY, radius, halfWidth, halfHeight);\n\t\tif(depth < best) {\n\t\t\tout.x = CANDIDATE.x;\n\t\t\tout.y = CANDIDATE.y;\n\t\t}\n\t}\n\n\tpointAwayFrom(localX, localY, out);\n\n\t// Back out of the box's frame.\n\tconst axisX = out.x;\n\tconst axisY = out.y;\n\tout.x = axisX * cos - axisY * sin;\n\tout.y = axisX * sin + axisY * cos;\n\n\treturn true;\n}\n\n// How far an axis-aligned box at the origin and a segment grown by `radius` run into each other along a unit\n// axis. The round side reaches its core's shadow plus the radius, which is the same in every direction.\nfunction roundOverlapAlong(\n\taxisX: number, axisY: number,\n\tcentreX: number, centreY: number,\n\thalfX: number, halfY: number, radius: number,\n\thalfWidth: number, halfHeight: number,\n): number {\n\tconst boxReach = halfWidth * Math.abs(axisX) + halfHeight * Math.abs(axisY);\n\tconst roundReach = Math.abs(halfX * axisX + halfY * axisY) + radius;\n\n\treturn boxReach + roundReach - Math.abs(centreX * axisX + centreY * axisY);\n}\n\n// Turns an axis the way that leads away from the other shape. A tie means the centres sit on the axis' line, and\n// either side is as good.\nfunction pointAwayFrom(offsetX: number, offsetY: number, out: Vector): void {\n\tif(offsetX * out.x + offsetY * out.y < 0) {\n\t\tout.x = -out.x;\n\t\tout.y = -out.y;\n\t}\n}\n\nfunction normalize(x: number, y: number, out: Vector): boolean {\n\tconst length = Math.sqrt(x * x + y * y);\n\tif(length === 0) {\n\t\treturn false;\n\t}\n\n\tout.x = x / length;\n\tout.y = y / length;\n\n\treturn true;\n}\n\n// Half the axis-aligned box containing a rotated rectangle: each side contributes however much now points along\n// the axis. width / 2 at angle 0, height / 2 at a quarter turn.\nexport function boundsHalfWidth(width: number, height: number, angle: number): number {\n\treturn (Math.abs(Math.cos(angle)) * width + Math.abs(Math.sin(angle)) * height) / 2;\n}\nexport function boundsHalfHeight(width: number, height: number, angle: number): number {\n\treturn (Math.abs(Math.sin(angle)) * width + Math.abs(Math.cos(angle)) * height) / 2;\n}\n\n// Whether two rotated boxes overlap, by the separating axis theorem: for two boxes only four axes can separate\n// them - the two each box's sides face along. Touching exactly and a zero-area box both count as not\n// overlapping, which keeps a sizeless entity out of collisions.\nexport function orientedBoxesOverlap(\n\taX: number, aY: number, aWidth: number, aHeight: number, aAngle: number,\n\tbX: number, bY: number, bWidth: number, bHeight: number, bAngle: number,\n): boolean {\n\tif(aWidth <= 0 || aHeight <= 0 || bWidth <= 0 || bHeight <= 0) {\n\t\treturn false;\n\t}\n\n\tconst aCos = Math.cos(aAngle);\n\tconst aSin = Math.sin(aAngle);\n\tconst bCos = Math.cos(bAngle);\n\tconst bSin = Math.sin(bAngle);\n\n\tconst aHalfWidth = aWidth / 2;\n\tconst aHalfHeight = aHeight / 2;\n\tconst bHalfWidth = bWidth / 2;\n\tconst bHalfHeight = bHeight / 2;\n\n\tconst offsetX = bX - aX;\n\tconst offsetY = bY - aY;\n\n\t// a's two axes, then b's; each box's sides face along (cos, sin) and (-sin, cos) of its angle.\n\treturn boxOverlapAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0\n\t\t&& boxOverlapAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin) > 0;\n}\n\n// How far the boxes' shadows on the axis run into each other; zero or less means the axis separates them, so\n// touching exactly does not overlap. The axes are always a box's own unit-vector sides, so the dot products are\n// true distances and nothing needs normalising. The depth is what picks the contact face in contactNormal.\nfunction boxOverlapAlong(\n\taxisX: number, axisY: number,\n\toffsetX: number, offsetY: number,\n\taHalfWidth: number, aHalfHeight: number, aCos: number, aSin: number,\n\tbHalfWidth: number, bHalfHeight: number, bCos: number, bSin: number,\n): number {\n\tconst aReach = aHalfWidth * Math.abs(aCos * axisX + aSin * axisY) + aHalfHeight * Math.abs(-aSin * axisX + aCos * axisY);\n\tconst bReach = bHalfWidth * Math.abs(bCos * axisX + bSin * axisY) + bHalfHeight * Math.abs(-bSin * axisX + bCos * axisY);\n\n\treturn aReach + bReach - Math.abs(offsetX * axisX + offsetY * axisY);\n}\n\n// Squared distance between two segments - squared so callers can compare against a squared radius without a\n// sqrt. Settles every round pair (a circle is a segment whose ends coincide). Solves the closest points on the\n// two infinite lines then clamps back into the segments; the parallel case pins one end and clamps the rest.\n// `out`, if given, is filled with the vector from b's closest point to a's - the direction a is pushed off b.\nexport function segmentSegmentDistanceSquared(\n\taX1: number, aY1: number, aX2: number, aY2: number,\n\tbX1: number, bY1: number, bX2: number, bY2: number,\n\tout?: Vector,\n): number {\n\tconst aDeltaX = aX2 - aX1;\n\tconst aDeltaY = aY2 - aY1;\n\tconst bDeltaX = bX2 - bX1;\n\tconst bDeltaY = bY2 - bY1;\n\tconst offsetX = aX1 - bX1;\n\tconst offsetY = aY1 - bY1;\n\n\tconst aLength = aDeltaX * aDeltaX + aDeltaY * aDeltaY;\n\tconst bLength = bDeltaX * bDeltaX + bDeltaY * bDeltaY;\n\tconst bOffset = bDeltaX * offsetX + bDeltaY * offsetY;\n\n\tlet aParam = 0;\n\tlet bParam = 0;\n\tif(aLength <= 0 && bLength <= 0) {\n\t\tif(out) {\n\t\t\tout.x = offsetX;\n\t\t\tout.y = offsetY;\n\t\t}\n\n\t\treturn offsetX * offsetX + offsetY * offsetY;\n\t} else if(aLength <= 0) {\n\t\tbParam = clamp(bOffset / bLength);\n\t} else {\n\t\tconst aOffset = aDeltaX * offsetX + aDeltaY * offsetY;\n\t\tif(bLength <= 0) {\n\t\t\taParam = clamp(-aOffset / aLength);\n\t\t} else {\n\t\t\tconst along = aDeltaX * bDeltaX + aDeltaY * bDeltaY;\n\t\t\t// Zero when the segments are parallel; pinning aParam at 0 then picks any point, all equally close.\n\t\t\tconst denominator = aLength * bLength - along * along;\n\t\t\tif(denominator !== 0) {\n\t\t\t\taParam = clamp((along * bOffset - aOffset * bLength) / denominator);\n\t\t\t}\n\n\t\t\tbParam = (along * aParam + bOffset) / bLength;\n\t\t\t// Clamping bParam moves b's closest point, so aParam has to be solved again.\n\t\t\tif(bParam < 0) {\n\t\t\t\tbParam = 0;\n\t\t\t\taParam = clamp(-aOffset / aLength);\n\t\t\t} else if(bParam > 1) {\n\t\t\t\tbParam = 1;\n\t\t\t\taParam = clamp((along - aOffset) / aLength);\n\t\t\t}\n\t\t}\n\t}\n\n\tconst gapX = (aX1 + aDeltaX * aParam) - (bX1 + bDeltaX * bParam);\n\tconst gapY = (aY1 + aDeltaY * aParam) - (bY1 + bDeltaY * bParam);\n\tif(out) {\n\t\tout.x = gapX;\n\t\tout.y = gapY;\n\t}\n\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Squared distance between a segment and a rotated box, measured in the box's frame where it is axis aligned.\n// Two disjoint convex shapes have their closest pair at a corner, so once the crossing case is ruled out the\n// answer is the nearest of each segment end to the box and each box corner to the segment.\n// `out`, if given, is filled with the world vector from the box's closest point to the segment's - the direction\n// the segment is pushed off the box. It is (0, 0) when the segment runs through the box, which has no such\n// direction; contactNormal falls back to a penetration axis there.\nexport function segmentBoxDistanceSquared(\n\tx1: number, y1: number, x2: number, y2: number,\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\n\tout?: Vector,\n): number {\n\tconst cos = Math.cos(boxAngle);\n\tconst sin = Math.sin(boxAngle);\n\tconst halfWidth = boxWidth / 2;\n\tconst halfHeight = boxHeight / 2;\n\n\tconst firstOffsetX = x1 - boxX;\n\tconst firstOffsetY = y1 - boxY;\n\tconst secondOffsetX = x2 - boxX;\n\tconst secondOffsetY = y2 - boxY;\n\tconst localX1 = firstOffsetX * cos + firstOffsetY * sin;\n\tconst localY1 = -firstOffsetX * sin + firstOffsetY * cos;\n\tconst localX2 = secondOffsetX * cos + secondOffsetY * sin;\n\tconst localY2 = -secondOffsetX * sin + secondOffsetY * cos;\n\n\t// A segment through the box has both ends outside and every corner off, so the nearest-pair search below\n\t// would report a real distance for two shapes already through each other; catch it first.\n\tif(!segmentMissesBox(localX1, localY1, localX2, localY2, halfWidth, halfHeight)) {\n\t\tif(out) {\n\t\t\tout.x = 0;\n\t\t\tout.y = 0;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\tif(!out) {\n\t\treturn Math.min(\n\t\t\tpointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight),\n\t\t\tpointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight),\n\t\t\tpointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\t\tpointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\t);\n\t}\n\n\t// The same six candidates, keeping the gap of the winner. The corner ones measure box-to-segment, the other\n\t// way round from the endpoint ones, so they are negated to leave every gap pointing off the box.\n\tlet best = pointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight, out);\n\tbest = keepCloser(best, pointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight, CANDIDATE), CANDIDATE, out, false);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\tbest = keepCloser(best, pointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2, CANDIDATE), CANDIDATE, out, true);\n\n\t// Back out of the box's frame, the gap having been measured inside it.\n\tconst localGapX = out.x;\n\tconst localGapY = out.y;\n\tout.x = localGapX * cos - localGapY * sin;\n\tout.y = localGapX * sin + localGapY * cos;\n\n\treturn best;\n}\n\n// Takes the candidate over what `out` holds when it is closer, flipping it when it was measured box-to-segment.\nfunction keepCloser(best: number, candidate: number, gap: Vector, out: Vector, flip: boolean): number {\n\tif(candidate >= best) {\n\t\treturn best;\n\t}\n\n\tout.x = flip ? -gap.x : gap.x;\n\tout.y = flip ? -gap.y : gap.y;\n\n\treturn candidate;\n}\n\n// `out`, if given, is filled with the vector from the segment's closest point to (x, y).\nexport function pointSegmentDistanceSquared(x: number, y: number, x1: number, y1: number, x2: number, y2: number, out?: Vector): number {\n\tconst deltaX = x2 - x1;\n\tconst deltaY = y2 - y1;\n\tconst length = deltaX * deltaX + deltaY * deltaY;\n\n\tconst along = length > 0 ? clamp(((x - x1) * deltaX + (y - y1) * deltaY) / length) : 0;\n\tconst gapX = x - (x1 + deltaX * along);\n\tconst gapY = y - (y1 + deltaY * along);\n\tif(out) {\n\t\tout.x = gapX;\n\t\tout.y = gapY;\n\t}\n\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Whether an origin-centred axis-aligned box and a segment fail to reach, by separating axis. Touching counts\n// as reaching. For a zero-length segment the normal is (0, 0), which never separates and leaves the two box\n// axes to decide whether the point is inside.\nfunction segmentMissesBox(x1: number, y1: number, x2: number, y2: number, halfWidth: number, halfHeight: number): boolean {\n\tif(Math.max(x1, x2) < -halfWidth || Math.min(x1, x2) > halfWidth) {\n\t\treturn true;\n\t} else if(Math.max(y1, y2) < -halfHeight || Math.min(y1, y2) > halfHeight) {\n\t\treturn true;\n\t}\n\n\tconst normalX = -(y2 - y1);\n\tconst normalY = x2 - x1;\n\n\treturn Math.abs(normalX * x1 + normalY * y1) > halfWidth * Math.abs(normalX) + halfHeight * Math.abs(normalY);\n}\n\n// Squared distance from a point to an origin-centred axis-aligned box; 0 inside it. `out`, if given, is filled\n// with the vector from the box's closest point to (x, y).\nfunction pointBoxDistanceSquared(x: number, y: number, halfWidth: number, halfHeight: number, out?: Vector): number {\n\tconst gapX = Math.abs(x) - halfWidth;\n\tconst gapY = Math.abs(y) - halfHeight;\n\tconst outsideX = gapX > 0 ? gapX : 0;\n\tconst outsideY = gapY > 0 ? gapY : 0;\n\tif(out) {\n\t\tout.x = x < 0 ? -outsideX : outsideX;\n\t\tout.y = y < 0 ? -outsideY : outsideY;\n\t}\n\n\treturn outsideX * outsideX + outsideY * outsideY;\n}\n\nfunction clamp(value: number): number {\n\tif(value < 0) {\n\t\treturn 0;\n\t} else if(value > 1) {\n\t\treturn 1;\n\t}\n\n\treturn value;\n}\n","import Flatbush from 'flatbush';\nimport { DEAD_INDEX } from '@daneren2005/shared-memory-ecs';\nimport type { ComponentMap, ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents } from '@daneren2005/shared-memory-ecs';\nimport type { PhysicsUpdateComponents } from '../components/registry';\nimport { BODY_CATEGORY_INDEX, BODY_MASK_INDEX, bodyShape, isContinuous, isSensor, SHAPE_CAPSULE, SHAPE_RECTANGLE } from '../components/body-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport { shapeHalfHeight, shapeHalfWidth, shapeIsEmpty, shapeRadius, shapesOverlap } from '../math/shapes';\n\n// How close to contact a sweep settles for, in world units: below anything a game would draw, yet reached by\n// the halving below in a handful of steps.\nconst CONTACT_TOLERANCE = 1e-4;\n// Cap on the halving so a huge move cannot spawn unbounded overlap tests; reaches the tolerance for any move\n// up to ~1600 units.\nconst MAX_REFINEMENTS = 24;\n\n// Holds everything with a transform and a body, not only the entities the system moves, so a mover can run\n// into a wall or station that has no velocity of its own.\nexport const COLLIDABLE_QUERY = 'collidable';\n\n// Blocks handed to a collision callback for the entity run into. `transform` and `body` are guaranteed by the\n// collidable query; `velocity` and anything in `optional` are present only if that entity has them.\nexport type CollisionComponents<T extends PhysicsUpdateComponents> = Partial<T> & {\n\ttransform: Float32Array\n\tbody: Uint32Array\n\tentity?: Uint32Array\n};\n\n// The entity doing the running into: the one being updated, so everything the system asked for is present.\nexport interface MovingEntity<T extends PhysicsUpdateComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// The entity on the other side of a collision, which may be something the system never moves.\nexport interface CollisionEntity<T extends PhysicsUpdateComponents> {\n\tentityId: number\n\tcomponents: CollisionComponents<T>\n}\n\n// What a game runs when one entity moves into another. Runs on the physics thread, so it is a plain function\n// over the raw blocks and reaches the main thread only through `callbacks`. Called once per pair per run, right\n// after the mover is put down: `self` is whichever side reached the contact first, `other` is the other. Because\n// the other side gets no call of its own, a callback has to decide for both. A velocity-less entity is never\n// `self` but is found as `other`.\nexport type CollisionFunction<C extends ComponentMap, T extends PhysicsUpdateComponents & EntityUpdateComponents<C>, W extends ComponentSystemWorld = ComponentSystemWorld> = (\n\tworld: W,\n\tself: MovingEntity<T>,\n\tother: CollisionEntity<T>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n) => void;\n\n// How much of a move an entity may make and what stopped it. `fraction` is how far along it got (1 clear, 0\n// already pressed against something). `blocking` is what it came to rest against - the overlap test there will\n// not report it, since the two are touching; more than one entry means it wedged between them at once.\nexport interface SweepResult<T extends PhysicsUpdateComponents> {\n\tfraction: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// How a move came out, as the displacement to apply rather than a fraction - a move that dropped an axis is no\n// longer a scaling of the original. `blocking` is empty for a move that got where it was going.\nexport interface MoveResult<T extends PhysicsUpdateComponents> {\n\tmoveX: number\n\tmoveY: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// One shared empty array for the sweeps that find nothing, rather than one per entity per run.\nconst NOTHING_BLOCKING: Array<never> = [];\n\n// Packs a pair of entity ids into one key. Ids must stay under this for the key to be unique, which a pool an\n// ECS can address leaves untouchable, and it keeps the product inside the safe integer range.\nconst MAX_PAIR_ID = 2 ** 26;\n\n// A per-axis sweep that hit nothing: the whole axis, clear. Held once rather than rebuilt per clear axis.\nconst CLEAR = { fraction: 1, blocking: NOTHING_BLOCKING };\n\n// The searching entity's blocks read out once, so a sweep testing the same pair repeatedly does not re-read\n// them. x/y are where it started the move.\ninterface Searcher {\n\tentityId: number\n\tshape: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n\thalfWidth: number\n\thalfHeight: number\n\tcategory: number\n\tmask: number\n\t// A sensor's move is never swept short: it passes through rather than resting against. Read once here.\n\tsensor: boolean\n\tccd: boolean\n}\n\n// One category's broadphase: every entity that collides as that category, plus an R-tree over them. A searcher\n// only visits buckets its mask accepts. Bucketed on the whole category value, so an entity that collides as two\n// things at once still lives in exactly one bucket and is never found twice by one search.\ninterface CategoryBucket<T extends PhysicsUpdateComponents> {\n\tcategory: number\n\tentries: Array<CollisionEntity<T>>\n\tindex: Flatbush\n}\n\n// An R-tree over every collidable entity, built once at the top of a run and then asked, per entity as it\n// moves, what it might have hit.\n//\n// Everything is indexed where it stood at the start of the run - the only moment all entities agree on. To stay\n// a superset of what can collide, each box is grown by how far that entity could travel this run, both ways: a\n// callback may turn an entity around before its own move, so a box grown only forwards would point wrong.\n//\n// Split by collide category so most of what an entity cannot hit is ruled out a subtree at a time. An empty\n// category has no bucket, so Flatbush is never asked to index zero items.\nexport default class CollisionBroadphase<T extends PhysicsUpdateComponents> {\n\tprivate buckets: Array<CategoryBucket<T>> = [];\n\t// Whether anything in this run can bounce. Bounces are resolved for both sides of a contact, so an entity with\n\t// no bounciness of its own still has to look at what it ran into - but only in a world where that can matter.\n\treadonly hasBounciness: boolean = false;\n\t// The pairs already resolved this run. Lives here rather than in the update because the tree is what a run is\n\t// scoped to: preRun builds a new one, so the set is empty again for the next run with nothing to clear.\n\tprivate resolved = new Set<number>();\n\n\tconstructor(entities: Array<{ entityId: number, components: EntityUpdateComponents }>, seconds: number) {\n\t\t// Collected per category first because Flatbush needs its item count up front, which is only known once\n\t\t// everything that cannot collide has been dropped.\n\t\tconst pending = new Map<number, { entries: Array<CollisionEntity<T>>, bounds: Array<number> }>();\n\n\t\tfor(const entity of entities) {\n\t\t\t// The ECS types blocks generically as ComponentTypedArray; narrow to the concrete arrays once here so\n\t\t\t// neither the search nor the game's callback needs a cast.\n\t\t\tconst components = entity.components as CollisionComponents<T>;\n\t\t\tconst transform = components.transform;\n\t\t\tconst body = components.body;\n\t\t\tif(!transform || !body) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// No mask can name category 0, so a category-0 entity can never be the other half of a collision;\n\t\t\t// dropping it here is exact and is what makes `collideCategory: 0` opt out entirely.\n\t\t\tconst category = body[BODY_CATEGORY_INDEX];\n\t\t\tif(category === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst shape = bodyShape(body);\n\t\t\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\t\t\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\t\t\t// A shape with no area can never overlap anything; dropping it keeps a sizeless entity out of the tree.\n\t\t\tif(shapeIsEmpty(shape, width, height)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\t\t\tconst halfWidth = shapeHalfWidth(shape, width, height, angle);\n\t\t\tconst halfHeight = shapeHalfHeight(shape, width, height, angle);\n\n\t\t\tif(components.bounciness) {\n\t\t\t\tthis.hasBounciness = true;\n\t\t\t}\n\n\t\t\tconst velocity = components.velocity;\n\t\t\tconst travelX = velocity ? Math.abs(velocity[VELOCITY_X_INDEX]) * seconds : 0;\n\t\t\tconst travelY = velocity ? Math.abs(velocity[VELOCITY_Y_INDEX]) * seconds : 0;\n\n\t\t\tconst x = transform[TRANSFORM_X_INDEX];\n\t\t\tconst y = transform[TRANSFORM_Y_INDEX];\n\n\t\t\tlet bucket = pending.get(category);\n\t\t\tif(!bucket) {\n\t\t\t\tbucket = { entries: [], bounds: [] };\n\t\t\t\tpending.set(category, bucket);\n\t\t\t}\n\n\t\t\tbucket.entries.push({ entityId: entity.entityId, components });\n\t\t\tbucket.bounds.push(\n\t\t\t\tx - halfWidth - travelX,\n\t\t\t\ty - halfHeight - travelY,\n\t\t\t\tx + halfWidth + travelX,\n\t\t\t\ty + halfHeight + travelY,\n\t\t\t);\n\t\t}\n\n\t\tfor(const [category, { entries, bounds }] of pending) {\n\t\t\tconst index = new Flatbush(entries.length);\n\t\t\tfor(let i = 0; i < entries.length; i++) {\n\t\t\t\tindex.add(bounds[i * 4], bounds[i * 4 + 1], bounds[i * 4 + 2], bounds[i * 4 + 3]);\n\t\t\t}\n\t\t\tindex.finish();\n\n\t\t\tthis.buckets.push({ category, entries, index });\n\t\t}\n\t}\n\n\t// Claims a contact for the caller: true the first time this run sees the pair, false every time after. Both\n\t// sides find the same contact - one sweeping into it, the other overlapping it on its own turn - and it is\n\t// resolved once, by whichever got there first, so nothing is bounced or reported twice.\n\tclaimContact(a: number, b: number): boolean {\n\t\tconst key = a < b ? a * MAX_PAIR_ID + b : b * MAX_PAIR_ID + a;\n\t\tif(this.resolved.has(key)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.resolved.add(key);\n\n\t\treturn true;\n\t}\n\n\t// Runs `handle` for everything `self` really overlaps, having just moved. The tree narrows the field, then\n\t// each candidate gets the full rotated box test against where both entities are now - which for one that has\n\t// not moved yet is still its start. That evens out: the pair is seen again from the other side once it moves.\n\t//\n\t// `moveX`/`moveY` is the move this entity just took, so a continuous-collision body can be tested along the way\n\tforEachOverlapping(self: MovingEntity<T>, handle: (other: CollisionEntity<T>) => void, moveX = 0, moveY = 0): void {\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { x, y, halfWidth, halfHeight } = searcher;\n\n\t\tif(searcher.ccd && (moveX !== 0 || moveY !== 0)) {\n\t\t\tconst startX = x - moveX;\n\t\t\tconst startY = y - moveY;\n\t\t\tconst minX = Math.min(startX, x) - halfWidth;\n\t\t\tconst minY = Math.min(startY, y) - halfHeight;\n\t\t\tconst maxX = Math.max(startX, x) + halfWidth;\n\t\t\tconst maxY = Math.max(startY, y) + halfHeight;\n\t\t\tthis.forEachCandidate(searcher, minX, minY, maxX, maxY, other => {\n\t\t\t\tif(sweptOverlaps(searcher, startX, startY, moveX, moveY, other)) {\n\t\t\t\t\thandle(other);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn;\n\t\t}\n\n\t\t// Searched ungrown at where this entity ended up: every tree box already allows for its own entity's move.\n\t\tthis.forEachCandidate(searcher, x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight, other => {\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\thandle(other);\n\t\t\t}\n\t\t});\n\t}\n\n\t// How much of the move `self` may take before it runs into something. The whole move is checked in one pass\n\t// for what it lands on, and only then is the resting place refined - so the answer is the earliest contact\n\t// of the lot, not whichever candidate came up first.\n\tsweep(self: MovingEntity<T>, moveX: number, moveY: number): SweepResult<T> {\n\t\tconst distance = Math.sqrt(moveX * moveX + moveY * moveY);\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher || distance === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\tif(searcher.ccd) {\n\t\t\treturn this.sweepContinuous(searcher, moveX, moveY, distance);\n\t\t}\n\n\t\tconst candidates = this.gatherCandidates(searcher, moveX, moveY);\n\t\tif(candidates.length === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\treturn this.refine(searcher, candidates, moveX, moveY, distance);\n\t}\n\n\t// Resolves a move as the displacement to write. With `slide` off it is `sweep` in other clothes: the whole\n\t// move, or the swept-short part. With `slide` on, a blocked diagonal is resolved one axis at a time so an\n\t// entity clipping a corner keeps running along the wall instead of sticking to it. Sliding is the caller's to\n\t// ask for: it is wrong for a bouncing entity, which turns around rather than skating along, so bounces pass\n\t// `slide` off.\n\tresolveMove(self: MovingEntity<T>, moveX: number, moveY: number, slide: boolean): MoveResult<T> {\n\t\tconst distance = Math.sqrt(moveX * moveX + moveY * moveY);\n\t\tconst searcher = toSearcher(self);\n\t\tif(!searcher || distance === 0) {\n\t\t\treturn { moveX, moveY, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\t// A continuous body comes straight to rest at its first contact, never sliding: sliding is for a steered\n\t\t// character clipping a corner at walking pace, not the fast thing ccd is switched on for, and the swept\n\t\t// path is a single decision over the whole move rather than one refined per axis.\n\t\tif(searcher.ccd) {\n\t\t\tconst rest = this.sweepContinuous(searcher, moveX, moveY, distance);\n\n\t\t\treturn { moveX: moveX * rest.fraction, moveY: moveY * rest.fraction, blocking: rest.blocking };\n\t\t}\n\n\t\tconst candidates = this.gatherCandidates(searcher, moveX, moveY);\n\t\tif(candidates.length === 0) {\n\t\t\treturn { moveX, moveY, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\t// Blocked diagonal, asked to slide: resolve each axis on its own to find which one was actually blocked.\n\t\tif(slide && moveX !== 0 && moveY !== 0) {\n\t\t\tconst xCandidates = this.gatherCandidates(searcher, moveX, 0);\n\t\t\tconst yCandidates = this.gatherCandidates(searcher, 0, moveY);\n\n\t\t\t// Corner clip: each axis alone is clear, so only the diagonal was caught. Slide the full length of the\n\t\t\t// dominant axis and leave the other - advancing it would walk back into the corner.\n\t\t\tif(xCandidates.length === 0 && yCandidates.length === 0) {\n\t\t\t\treturn Math.abs(moveX) >= Math.abs(moveY)\n\t\t\t\t\t? { moveX, moveY: 0, blocking: NOTHING_BLOCKING }\n\t\t\t\t\t: { moveX: 0, moveY, blocking: NOTHING_BLOCKING };\n\t\t\t}\n\n\t\t\t// At least one axis is blocked. Take each as far as it goes - a blocked one refined up to its edge, not\n\t\t\t// dropped to nothing - so the entity ends hard against the obstacle.\n\t\t\tconst x = xCandidates.length === 0 ? CLEAR : this.refine(searcher, xCandidates, moveX, 0, Math.abs(moveX));\n\t\t\tconst y = yCandidates.length === 0 ? CLEAR : this.refine(searcher, yCandidates, 0, moveY, Math.abs(moveY));\n\n\t\t\tconst slidX = moveX * x.fraction;\n\t\t\tconst slidY = moveY * y.fraction;\n\n\t\t\t// The axes were swept apart; their combination can still poke into a convex (round) obstacle neither axis\n\t\t\t// alone reached - sliding the full length of one axis carries the entity into the curve the other axis\n\t\t\t// stopped short of. Left overlapping, next run's start-overlap escape hatch would let it pass straight\n\t\t\t// through, so the combined slide is checked against where it actually lands and refined to contact if it\n\t\t\t// overlaps. A box slide ends touching an axis-aligned face and finds nothing here.\n\t\t\tif(slidX !== 0 && slidY !== 0) {\n\t\t\t\tconst slidCandidates = this.gatherCandidates(searcher, slidX, slidY);\n\t\t\t\tif(slidCandidates.length > 0) {\n\t\t\t\t\tconst rest = this.refine(searcher, slidCandidates, slidX, slidY, Math.sqrt(slidX * slidX + slidY * slidY));\n\n\t\t\t\t\treturn { moveX: slidX * rest.fraction, moveY: slidY * rest.fraction, blocking: rest.blocking };\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn { moveX: slidX, moveY: slidY, blocking: mergeBlocking(x.blocking, y.blocking) };\n\t\t}\n\n\t\t// Not sliding, or a straight move: come to rest where it stops.\n\t\tconst rest = this.refine(searcher, candidates, moveX, moveY, distance);\n\n\t\treturn { moveX: moveX * rest.fraction, moveY: moveY * rest.fraction, blocking: rest.blocking };\n\t}\n\n\t// The cheap half of a sweep: everything the move would land on, tested only where it ends. That keeps a clear\n\t// move - nearly every move - to one shape test per candidate. The trade is that a move long enough to carry\n\t// an entity clean past something is not stopped by it, which a fixed `deltaBetweenRuns` rules out.\n\tprivate gatherCandidates(searcher: Searcher, moveX: number, moveY: number): Array<CollisionEntity<T>> {\n\t\tconst { x, y, halfWidth, halfHeight } = searcher;\n\n\t\t// A sensor's move is blocked by nothing; it is still reported by forEachOverlapping, but never gathered.\n\t\tif(searcher.sensor) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst endX = x + moveX;\n\t\tconst endY = y + moveY;\n\n\t\tconst candidates: Array<CollisionEntity<T>> = [];\n\t\tthis.forEachCandidate(searcher, endX - halfWidth, endY - halfHeight, endX + halfWidth, endY + halfHeight, other => {\n\t\t\tif(!overlapsAt(searcher, endX, endY, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// A sensor blocks nothing: a solid mover passes through it, left to the overlap callback.\n\t\t\tif(isSensor(other.components.body)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Already inside it before the move began: it is not what this move ran into, and blocking on it would\n\t\t\t// pin the entity inside with no way out. Let the move through and leave the pair to the overlap callback.\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tcandidates.push(other);\n\t\t});\n\n\t\treturn candidates;\n\t}\n\n\t// The expensive half: the move is known to stop short, and this finds where by halving between a `clear`\n\t// fraction and a `blocked` one. Contact is halved in on rather than solved - three shapes at any rotation\n\t// have no single first-touch formula - and the result is always taken from a position that tested clear.\n\tprivate refine(searcher: Searcher, candidates: Array<CollisionEntity<T>>, moveX: number, moveY: number, distance: number): SweepResult<T> {\n\t\tconst { x, y } = searcher;\n\t\tlet clear = 0;\n\t\tlet blocked = 1;\n\t\tconst tolerance = CONTACT_TOLERANCE / distance;\n\t\tfor(let i = 0; i < MAX_REFINEMENTS && blocked - clear > tolerance; i++) {\n\t\t\tconst middle = (clear + blocked) / 2;\n\t\t\tif(this.blockedAt(searcher, candidates, moveX, moveY, middle)) {\n\t\t\t\tblocked = middle;\n\t\t\t} else {\n\t\t\t\tclear = middle;\n\t\t\t}\n\t\t}\n\n\t\t// Whatever is in the way at the first blocked position: the entity rests a hair short of it.\n\t\tconst blocking: Array<CollisionEntity<T>> = [];\n\t\tconst blockedX = x + moveX * blocked;\n\t\tconst blockedY = y + moveY * blocked;\n\t\tfor(const other of candidates) {\n\t\t\tif(overlapsAt(searcher, blockedX, blockedY, other)) {\n\t\t\t\tblocking.push(other);\n\t\t\t}\n\t\t}\n\n\t\t// An entity already against something is left exactly where it is, rather than crawling a sub-tolerance\n\t\t// step forward every run and reporting a position change for no visible move.\n\t\treturn { fraction: clear * distance > CONTACT_TOLERANCE ? clear : 0, blocking };\n\t}\n\n\t// The continuous counterpart to gatherCandidates: everything the move sweeps *across*, not only what it ends\n\t// on, so a step long enough to fly clean past something still gathers it. Costs the swept-shape test per\n\t// candidate, which is why it is a body's own opt-in rather than the default.\n\tprivate gatherContinuousCandidates(searcher: Searcher, moveX: number, moveY: number): Array<CollisionEntity<T>> {\n\t\t// A sensor is stopped by nothing; its overlaps are still reported by forEachOverlapping, never gathered here.\n\t\tif(searcher.sensor) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst { x, y, halfWidth, halfHeight } = searcher;\n\t\tconst endX = x + moveX;\n\t\tconst endY = y + moveY;\n\n\t\tconst candidates: Array<CollisionEntity<T>> = [];\n\t\tthis.forEachCandidate(searcher, Math.min(x, endX) - halfWidth, Math.min(y, endY) - halfHeight, Math.max(x, endX) + halfWidth, Math.max(y, endY) + halfHeight, other => {\n\t\t\t// A sensor blocks nothing: a solid mover passes through it, left to the overlap callback.\n\t\t\tif(isSensor(other.components.body)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Already inside it before the move began: not what this move ran into, and blocking on it would pin the\n\t\t\t// entity there. Let the move through and leave the pair to the overlap callback.\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif(sweptOverlaps(searcher, x, y, moveX, moveY, other)) {\n\t\t\t\tcandidates.push(other);\n\t\t\t}\n\t\t});\n\n\t\treturn candidates;\n\t}\n\n\t// A continuous move's resting place: the earliest contact of everything it swept across. Each candidate's own\n\t// entry fraction is found and the whole move stops at the smallest of them - the near obstacle, not whichever\n\t// the tree happened to turn up first.\n\tprivate sweepContinuous(searcher: Searcher, moveX: number, moveY: number, distance: number): SweepResult<T> {\n\t\tconst candidates = this.gatherContinuousCandidates(searcher, moveX, moveY);\n\t\tif(candidates.length === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\tlet earliest = 1;\n\t\tlet blocking: Array<CollisionEntity<T>> = [];\n\t\tconst tolerance = CONTACT_TOLERANCE / distance;\n\t\tfor(const other of candidates) {\n\t\t\tconst fraction = this.entryFraction(searcher, other, moveX, moveY, distance);\n\t\t\tif(fraction < earliest - tolerance) {\n\t\t\t\tearliest = fraction;\n\t\t\t\tblocking = [other];\n\t\t\t} else if(fraction <= earliest + tolerance) {\n\t\t\t\tblocking.push(other);\n\t\t\t}\n\t\t}\n\n\t\t// An entity already against something is left exactly where it is rather than creeping a sub-tolerance step\n\t\t// forward every run, same as refine.\n\t\treturn { fraction: earliest * distance > CONTACT_TOLERANCE ? earliest : 0, blocking };\n\t}\n\n\t// The fraction of its move at which `searcher` first touches `other`, halved in the way refine does. What refine\n\t// cannot assume here is that the far end is blocked: a continuous move may pass clean through, clear at both\n\t// ends, so the blocked bracket is seeded from where the two centres pass closest - which for convex shapes that\n\t// really cross lies inside the overlap - rather than from the end of the move.\n\tprivate entryFraction(searcher: Searcher, other: CollisionEntity<T>, moveX: number, moveY: number, distance: number): number {\n\t\tlet blocked: number;\n\t\tif(this.blockedAtOne(searcher, other, moveX, moveY, 1)) {\n\t\t\tblocked = 1;\n\t\t} else {\n\t\t\tconst transform = other.components.transform;\n\t\t\tconst toX = transform[TRANSFORM_X_INDEX] - searcher.x;\n\t\t\tconst toY = transform[TRANSFORM_Y_INDEX] - searcher.y;\n\t\t\tconst closest = clamp01((toX * moveX + toY * moveY) / (distance * distance));\n\t\t\t// The swept shape crossed the candidate but the centres' closest pass is not itself an overlap (a\n\t\t\t// glancing hit off the centre line): stop there rather than search on. It rests a hair early at worst,\n\t\t\t// which never tunnels.\n\t\t\tif(!this.blockedAtOne(searcher, other, moveX, moveY, closest)) {\n\t\t\t\treturn closest;\n\t\t\t}\n\n\t\t\tblocked = closest;\n\t\t}\n\n\t\tlet clear = 0;\n\t\tconst tolerance = CONTACT_TOLERANCE / distance;\n\t\tfor(let i = 0; i < MAX_REFINEMENTS && blocked - clear > tolerance; i++) {\n\t\t\tconst middle = (clear + blocked) / 2;\n\t\t\tif(this.blockedAtOne(searcher, other, moveX, moveY, middle)) {\n\t\t\t\tblocked = middle;\n\t\t\t} else {\n\t\t\t\tclear = middle;\n\t\t\t}\n\t\t}\n\n\t\treturn clear;\n\t}\n\n\t// blockedAt for a single candidate, so the continuous refinement halves without allocating a one-item array per\n\t// step. The position is rounded to float32 for the same reason blockedAt is - the resting place must stay clear\n\t// once stored to the shared transform.\n\tprivate blockedAtOne(searcher: Searcher, other: CollisionEntity<T>, moveX: number, moveY: number, fraction: number): boolean {\n\t\treturn overlapsAt(searcher, Math.fround(searcher.x + moveX * fraction), Math.fround(searcher.y + moveY * fraction), other);\n\t}\n\n\t// Whether `self` would be inside any of `candidates` that far along its move. The position is rounded to\n\t// float32 - what `move` actually stores into the shared transform - so the resting place this settles on is\n\t// still clear once stored, not a hair inside. Otherwise a float32 nudge into overlap would trip the\n\t// start-overlap escape hatch next run and let the entity pass straight through what it came to rest against.\n\tprivate blockedAt(searcher: Searcher, candidates: Array<CollisionEntity<T>>, moveX: number, moveY: number, fraction: number): boolean {\n\t\tconst x = Math.fround(searcher.x + moveX * fraction);\n\t\tconst y = Math.fround(searcher.y + moveY * fraction);\n\t\tfor(const other of candidates) {\n\t\t\tif(overlapsAt(searcher, x, y, other)) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t// Runs `handle` for every collidable entity in the box that `searcher` may collide with: the tree search and\n\t// the category masks, applied before any shape test so an impossible pair is settled by two ANDs.\n\tprivate forEachCandidate(searcher: Searcher, minX: number, minY: number, maxX: number, maxY: number, handle: (other: CollisionEntity<T>) => void): void {\n\t\tfor(const bucket of this.buckets) {\n\t\t\t// Half of canCollide, settled once per bucket: all entries share this category.\n\t\t\tif((searcher.mask & bucket.category) === 0) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tfor(const found of bucket.index.search(minX, minY, maxX, maxY)) {\n\t\t\t\tconst other = bucket.entries[found];\n\t\t\t\tif(other.entityId === searcher.entityId) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// The other half of canCollide, per candidate: each entity carries its own mask.\n\t\t\t\tif((other.components.body[BODY_MASK_INDEX] & searcher.category) === 0) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Already killed this run, or by another worker sharing this block.\n\t\t\t\tif(other.components.entity?.[DEAD_INDEX] === 1) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\thandle(other);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Merges what stopped each axis of a slide, without repeating an entity that stopped both. Either side empty is\n// the common case, so it is handed straight back rather than copied.\nfunction mergeBlocking<T extends PhysicsUpdateComponents>(a: Array<CollisionEntity<T>>, b: Array<CollisionEntity<T>>): Array<CollisionEntity<T>> {\n\tif(a.length === 0) {\n\t\treturn b;\n\t}\n\tif(b.length === 0) {\n\t\treturn a;\n\t}\n\n\tconst merged = [...a];\n\tfor(const other of b) {\n\t\tif(!merged.some(entry => entry.entityId === other.entityId)) {\n\t\t\tmerged.push(other);\n\t\t}\n\t}\n\n\treturn merged;\n}\n\n// Reads what a searching entity is measured by, or undefined for one that cannot collide.\nfunction toSearcher<T extends PhysicsUpdateComponents>(self: MovingEntity<T>): Searcher | undefined {\n\t// No body means no collisions, though the system can still move it - the move query does not require a body.\n\tconst body = self.components.body;\n\tif(!body) {\n\t\treturn undefined;\n\t}\n\n\t// Mask 0 runs into nothing, so no bucket is worth searching. This only stops it finding a collision; something\n\t// whose mask accepts it can still find this one, an asymmetry the symmetric canCollide rule then rules out.\n\tconst mask = body[BODY_MASK_INDEX];\n\tif(mask === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst transform = self.components.transform;\n\tconst shape = bodyShape(body);\n\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\tif(shapeIsEmpty(shape, width, height)) {\n\t\treturn undefined;\n\t}\n\n\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\n\treturn {\n\t\tentityId: self.entityId,\n\t\tshape,\n\t\tx: transform[TRANSFORM_X_INDEX],\n\t\ty: transform[TRANSFORM_Y_INDEX],\n\t\twidth,\n\t\theight,\n\t\tangle,\n\t\thalfWidth: shapeHalfWidth(shape, width, height, angle),\n\t\thalfHeight: shapeHalfHeight(shape, width, height, angle),\n\t\tcategory: body[BODY_CATEGORY_INDEX],\n\t\tmask,\n\t\tsensor: isSensor(body),\n\t\tccd: isContinuous(body),\n\t};\n}\n\n// Whether `searcher`, swept from (startX, startY) along (moveX, moveY), crosses `other` anywhere along the way -\n// the single extra test a continuous-collision body pays per candidate. Each shape sweeps to one covering shape:\n// rectangle - an oriented box holding its start, its end, and everything between, since a box that does not turn\n// as it moves just slides in its own frame and the region it covers is itself grown by that slide.\n// circle - a capsule down the centre path, which is exactly a moving circle's swept region.\n// capsule - that same path capsule, plus its own destination shape for the far end of the pill the straight\n// path capsule falls short of.\n// Every shape's region is a superset of the true swept area, so a contact is only ever reported early, never\n// missed - which is the whole point of turning ccd on.\nfunction sweptOverlaps<T extends PhysicsUpdateComponents>(searcher: Searcher, startX: number, startY: number, moveX: number, moveY: number, other: CollisionEntity<T>): boolean {\n\tconst endX = startX + moveX;\n\tconst endY = startY + moveY;\n\tconst transform = other.components.transform;\n\tconst otherShape = bodyShape(other.components.body);\n\tconst otherX = transform[TRANSFORM_X_INDEX];\n\tconst otherY = transform[TRANSFORM_Y_INDEX];\n\tconst otherWidth = transform[TRANSFORM_WIDTH_INDEX];\n\tconst otherHeight = transform[TRANSFORM_HEIGHT_INDEX];\n\tconst otherAngle = transform[TRANSFORM_ANGLE_INDEX];\n\n\tif(searcher.shape === SHAPE_RECTANGLE) {\n\t\t// The move measured in the box's own frame, where the box is axis aligned and merely slides by it. Growing\n\t\t// each side by that slide over-covers the two corners the slid hexagon leaves open, which reports early, not\n\t\t// short.\n\t\tconst cos = Math.cos(searcher.angle);\n\t\tconst sin = Math.sin(searcher.angle);\n\t\tconst localX = moveX * cos + moveY * sin;\n\t\tconst localY = -moveX * sin + moveY * cos;\n\n\t\treturn shapesOverlap(\n\t\t\tSHAPE_RECTANGLE, (startX + endX) / 2, (startY + endY) / 2,\n\t\t\tsearcher.width + Math.abs(localX), searcher.height + Math.abs(localY), searcher.angle,\n\t\t\totherShape, otherX, otherY, otherWidth, otherHeight, otherAngle,\n\t\t);\n\t}\n\n\t// A capsule of the mover's own radius laid down the centre path: width is the path length plus a cap at each\n\t// end, height the full thickness. For a circle that is the exact swept region; for a capsule the destination\n\t// test below adds back the length of the pill it does not cover.\n\tconst radius = shapeRadius(searcher.shape, searcher.width, searcher.height);\n\tconst distance = Math.sqrt(moveX * moveX + moveY * moveY);\n\tconst pathAngle = distance > 0 ? Math.atan2(moveY, moveX) : searcher.angle;\n\tconst alongPath = shapesOverlap(\n\t\tSHAPE_CAPSULE, (startX + endX) / 2, (startY + endY) / 2, distance + radius * 2, radius * 2, pathAngle,\n\t\totherShape, otherX, otherY, otherWidth, otherHeight, otherAngle,\n\t);\n\tif(alongPath) {\n\t\treturn true;\n\t}\n\n\treturn shapesOverlap(\n\t\tsearcher.shape, endX, endY, searcher.width, searcher.height, searcher.angle,\n\t\totherShape, otherX, otherY, otherWidth, otherHeight, otherAngle,\n\t);\n}\n\n// [0, 1] clamp for the closest-approach fraction; the shapes helper keeps its own copy unexported.\nfunction clamp01(value: number): number {\n\tif(value < 0) {\n\t\treturn 0;\n\t} else if(value > 1) {\n\t\treturn 1;\n\t}\n\n\treturn value;\n}\n\n// Whether the searcher, put down at (x, y) rather than its own block's position, overlaps `other`. Taking the\n// position as arguments lets a sweep try a whole path without writing any of it.\nfunction overlapsAt<T extends PhysicsUpdateComponents>(searcher: Searcher, x: number, y: number, other: CollisionEntity<T>): boolean {\n\tconst transform = other.components.transform;\n\n\treturn shapesOverlap(\n\t\tsearcher.shape, x, y, searcher.width, searcher.height, searcher.angle,\n\t\tbodyShape(other.components.body),\n\t\ttransform[TRANSFORM_X_INDEX], transform[TRANSFORM_Y_INDEX],\n\t\ttransform[TRANSFORM_WIDTH_INDEX], transform[TRANSFORM_HEIGHT_INDEX], transform[TRANSFORM_ANGLE_INDEX],\n\t);\n}\n","import Flatbush from 'flatbush';\nimport type { EntityUpdateComponents } from '@daneren2005/shared-memory-ecs';\nimport { bodyShape, SHAPE_RECTANGLE } from '../components/body-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { shapeHalfHeight, shapeHalfWidth } from '../math/shapes';\n\n// The blocks the index reads off each entity. Only the transform is needed; the body refines the box to the\n// entity's actual shape. Anything else a game queried travels along untouched for a filter to read.\nexport type SpatialComponents = {\n\ttransform: Float32Array\n\tbody?: Uint32Array\n};\n\n// One entity as the index hands it back: its id plus whatever blocks came with it.\nexport interface SpatialEntity<T extends SpatialComponents = SpatialComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// Which found entities are wanted. Run per candidate, so it is the place for what the index cannot know - the\n// searcher's own id, which team, whether it is dead - and a reject is as if it were never in the box.\nexport type SpatialFilter<T extends SpatialComponents = SpatialComponents> = (entity: SpatialEntity<T>) => boolean;\n\n// An R-tree over a set of entities, for questions about where things are rather than what is hitting what: who\n// is in this area, and what is nearest a point. The same machinery as the collision broadphase minus everything\n// collision specific, so answers are in terms of each entity's axis-aligned box and a `filter` picks the ones\n// that matter. It is a snapshot - nothing reads the blocks after the constructor - so build one per run.\n//\n// const index = new SpatialIndex(queries.collidable);\n// const nearest = index.findNearest(x, y, 150, other => other.entityId !== entityId);\nexport default class SpatialIndex<T extends SpatialComponents = SpatialComponents> {\n\tprivate entries: Array<SpatialEntity<T>> = [];\n\t// Left off for an empty index: Flatbush cannot hold zero items, and there is no answer to give anyway.\n\tprivate index: Flatbush | undefined;\n\n\t// `entities` is anything shaped like a query result, so a system's own `queries.whatever` goes straight in.\n\t// Entities without a transform are skipped - something with no place in the world cannot be found by a search\n\t// of it.\n\tconstructor(entities: Iterable<{ entityId: number, components: EntityUpdateComponents }>) {\n\t\t// Collected up front because Flatbush needs its item count before any are added, only known once everything\n\t\t// without a transform is dropped.\n\t\tconst bounds: Array<number> = [];\n\n\t\tfor(const entity of entities) {\n\t\t\t// The ECS types blocks generically as ComponentTypedArray; narrow to the concrete arrays once here.\n\t\t\tconst components = entity.components as T;\n\t\t\tconst transform = components.transform;\n\t\t\tif(!transform) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// No body means the transform's width and height are a plain rectangle, keeping the index usable for\n\t\t\t// anything with a place in the world, not only things that collide.\n\t\t\tconst body = components.body;\n\t\t\tconst shape = body ? bodyShape(body) : SHAPE_RECTANGLE;\n\t\t\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\t\t\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\t\t\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\t\t\tconst x = transform[TRANSFORM_X_INDEX];\n\t\t\tconst y = transform[TRANSFORM_Y_INDEX];\n\n\t\t\t// Unlike the collision broadphase, a zero-area entity is kept: it still has a position to be found at.\n\t\t\tconst halfWidth = shapeHalfWidth(shape, width, height, angle);\n\t\t\tconst halfHeight = shapeHalfHeight(shape, width, height, angle);\n\n\t\t\tthis.entries.push({ entityId: entity.entityId, components });\n\t\t\tbounds.push(x - halfWidth, y - halfHeight, x + halfWidth, y + halfHeight);\n\t\t}\n\n\t\tif(this.entries.length === 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst index = new Flatbush(this.entries.length);\n\t\tfor(let i = 0; i < this.entries.length; i++) {\n\t\t\tindex.add(bounds[i * 4], bounds[i * 4 + 1], bounds[i * 4 + 2], bounds[i * 4 + 3]);\n\t\t}\n\t\tindex.finish();\n\n\t\tthis.index = index;\n\t}\n\n\t// How many entities are in the index, which is not how many were handed to it: transform-less ones are out.\n\tget size(): number {\n\t\treturn this.entries.length;\n\t}\n\n\t// Everything whose box reaches into the one given, in no particular order. Touching counts, so an exact\n\t// edge question is the caller's to settle on the results.\n\tsearch(minX: number, minY: number, maxX: number, maxY: number, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\tconst index = this.index;\n\t\tif(!index) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = this.entries;\n\t\t// Filtering inside the search keeps a reject from ever reaching the results array.\n\t\tconst found = filter ? index.search(minX, minY, maxX, maxY, i => filter(entries[i]))\n\t\t\t: index.search(minX, minY, maxX, maxY);\n\n\t\treturn found.map(i => entries[i]);\n\t}\n\n\t// `search` from the middle out: everything within `reachX` / `reachY` of (x, y), the form most searches take.\n\tsearchAround(x: number, y: number, reachX: number, reachY: number, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\treturn this.search(x - reachX, y - reachY, x + reachX, y + reachY, filter);\n\t}\n\n\t// The closest entity to (x, y) that `filter` accepts, or undefined when none is within `maxDistance`. A walk\n\t// of the tree in distance order rather than a search-then-sort, so it never measures the far side of the\n\t// world. Distance is to the entity's box, not its centre, so a big thing is as near as its nearest edge.\n\tfindNearest(x: number, y: number, maxDistance = Infinity, filter?: SpatialFilter<T>): SpatialEntity<T> | undefined {\n\t\treturn this.findNearby(x, y, 1, maxDistance, filter)[0];\n\t}\n\n\t// The `maxResults` closest entities to (x, y), nearest first, on the same terms as findNearest.\n\tfindNearby(x: number, y: number, maxResults: number, maxDistance = Infinity, filter?: SpatialFilter<T>): Array<SpatialEntity<T>> {\n\t\tconst index = this.index;\n\t\tif(!index) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst entries = this.entries;\n\t\t// Handed to Flatbush rather than applied afterwards, so `maxResults` counts wanted entities: filtering after\n\t\t// would let a few rejects use up the allowance and hide a valid target behind them.\n\t\tconst found = filter ? index.neighbors(x, y, maxResults, maxDistance, i => filter(entries[i]))\n\t\t\t: index.neighbors(x, y, maxResults, maxDistance);\n\n\t\treturn found.map(i => entries[i]);\n\t}\n}\n","import { bodyShape, isSensor, SHAPE_RECTANGLE } from '../components/body-component';\nimport { BOUNCINESS_INDEX } from '../components/bounciness-component';\nimport { TRANSFORM_ANGLE_INDEX, TRANSFORM_HEIGHT_INDEX, TRANSFORM_WIDTH_INDEX, TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport { contactNormal } from '../math/shapes';\nimport type { Vector } from '../math/shapes';\n\ninterface BounceEntity {\n\tcomponents: {\n\t\ttransform: Float32Array\n\t\tvelocity?: Float32Array\n\t\tbounciness?: Float32Array\n\t\tbody?: Uint32Array\n\t}\n}\n\n// Scratch, reused rather than allocated: a run is one unbroken pass, never re-entered part way through.\nconst NORMAL: Vector = { x: 0, y: 0 };\n\n// Turns both sides of a contact around, each by its own bounciness. The two leave along the same line, so the\n// normal is worked out once and the far side is turned round rather than asked for again.\nexport function bouncePair(self: BounceEntity, other: BounceEntity): void {\n\tif(!canBounce(self) && !canBounce(other)) {\n\t\treturn;\n\t}\n\n\tif(isSurface(self, other) && collisionNormal(self, other, NORMAL)) {\n\t\treflect(self, NORMAL.x, NORMAL.y);\n\t\treflect(other, -NORMAL.x, -NORMAL.y);\n\t}\n}\n\n// Turns `self` around off `other` and writes the new heading into its velocity block. `bounciness` is how much\n// of the speed into the surface comes back: 1 reflects perfectly, 0 cancels it (slide along the surface) - see\n// BouncinessComponent. Only `self` is touched.\nexport function bounce(self: BounceEntity, other: BounceEntity): void {\n\tif(!canBounce(self) || !isSurface(self, other)) {\n\t\treturn;\n\t}\n\n\tif(collisionNormal(self, other, NORMAL)) {\n\t\treflect(self, NORMAL.x, NORMAL.y);\n\t}\n}\n\n// Mirrors an entity's velocity about a surface it is leaving along `normal`, a unit vector pointing away from it.\nfunction reflect(entity: BounceEntity, normalX: number, normalY: number): void {\n\tconst bounciness = entity.components.bounciness;\n\tconst velocity = entity.components.velocity;\n\tif(!bounciness || !velocity) {\n\t\treturn;\n\t}\n\n\tconst velocityX = velocity[VELOCITY_X_INDEX];\n\tconst velocityY = velocity[VELOCITY_Y_INDEX];\n\n\t// How much of the velocity points into what it hit. Zero or more means it is already on its way out, and\n\t// reflecting again while the two still overlap would turn it straight back in.\n\tconst into = velocityX * normalX + velocityY * normalY;\n\tif(into >= 0) {\n\t\treturn;\n\t}\n\n\t// Reverse the normal component and scale by bounciness, leaving the surface component alone. Scale is 2 at\n\t// bounciness 1 (mirror reflection) and 1 at 0 (removes the normal component, sliding along the surface).\n\tconst scale = (1 + bounciness[BOUNCINESS_INDEX]) * into;\n\tvelocity[VELOCITY_X_INDEX] = velocityX - scale * normalX;\n\tvelocity[VELOCITY_Y_INDEX] = velocityY - scale * normalY;\n}\n\nfunction canBounce(entity: BounceEntity): boolean {\n\treturn entity.components.bounciness !== undefined && entity.components.velocity !== undefined;\n}\n\n// A sensor is felt only through onCollision, never as a surface, so neither side bounces. The pair is still\n// reported - this decides the bounce, not the collision.\nfunction isSurface(self: BounceEntity, other: BounceEntity): boolean {\n\treturn !isSensorBody(self.components.body) && !isSensorBody(other.components.body);\n}\n\n// Which way `self` is pushed off `other`, as a unit vector, or false when the two are exactly on top of each\n// other and there is no such direction. Taken off the shapes themselves, at the angle they are turned to, so a\n// contact that is not square to the world still reflects the speed that was closing on it.\nfunction collisionNormal(self: BounceEntity, other: BounceEntity, out: Vector): boolean {\n\tconst selfTransform = self.components.transform;\n\tconst otherTransform = other.components.transform;\n\n\treturn contactNormal(\n\t\tshapeOf(self.components.body),\n\t\tselfTransform[TRANSFORM_X_INDEX], selfTransform[TRANSFORM_Y_INDEX],\n\t\tselfTransform[TRANSFORM_WIDTH_INDEX], selfTransform[TRANSFORM_HEIGHT_INDEX], selfTransform[TRANSFORM_ANGLE_INDEX],\n\t\tshapeOf(other.components.body),\n\t\totherTransform[TRANSFORM_X_INDEX], otherTransform[TRANSFORM_Y_INDEX],\n\t\totherTransform[TRANSFORM_WIDTH_INDEX], otherTransform[TRANSFORM_HEIGHT_INDEX], otherTransform[TRANSFORM_ANGLE_INDEX],\n\t\tout,\n\t);\n}\n\n// A size-only entity with no body collides as a rectangle.\nfunction shapeOf(body: Uint32Array | undefined): number {\n\treturn body ? bodyShape(body) : SHAPE_RECTANGLE;\n}\n\n// A missing body is a solid, not a sensor.\nfunction isSensorBody(body: Uint32Array | undefined): boolean {\n\treturn body !== undefined && isSensor(body);\n}\n\nexport default bounce;\n","import { addAtomicFloat32 } from '@daneren2005/shared-memory-objects/utils/atomic-math';\nimport { storeFloat32 } from '@daneren2005/shared-memory-objects/utils/float32-atomics';\nimport { DEAD_INDEX } from '@daneren2005/shared-memory-ecs';\nimport type { ComponentMap, ComponentSystemCallbacks, ComponentSystemWorld, EntityQueryComponents, EntityUpdateComponents, EntityUpdateFunction } from '@daneren2005/shared-memory-ecs';\nimport type { PhysicsComponents, PhysicsUpdateComponents } from '../components/registry';\nimport {\n\tINTERPOLATION_DURATION_INDEX,\n\tINTERPOLATION_PREV_X_INDEX,\n\tINTERPOLATION_PREV_Y_INDEX,\n\tINTERPOLATION_TICK_INDEX,\n} from '../components/interpolation-component';\nimport { TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\nimport { VELOCITY_X_INDEX, VELOCITY_Y_INDEX } from '../components/velocity-component';\nimport CollisionBroadphase, { COLLIDABLE_QUERY, type CollisionEntity, type CollisionFunction, type MovingEntity } from './collision';\nimport { bouncePair } from './bounce';\n\n// The per-run data object every physics update is handed: the base one plus the step counter the interpolation\n// component is stamped with. PhysicsSystem fills `tick` from addDataToWorld, so a subclass adding data must\n// call `super.addDataToWorld(world)` or nothing gets a tick and interpolation stops noticing new steps.\nexport interface PhysicsWorld extends ComponentSystemWorld {\n\t// Which physics step this is, bumped per run. Only compared for equality - a publication stamp, not a clock.\n\ttick: number\n\t// Whether to report this run's moves. Set by PhysicsSystem from whether anything is listening, since the cost\n\t// is paid in the worker (an id per moved entity, cloned across the boundary). Undefined means report.\n\treportMoves?: boolean\n}\n\n// The event a move is reported through, emitted on the system on the main thread once the run completes,\n// carrying the ids of everything that moved:\n//\n// physicsSystem.on(POSITION_UPDATED_EVENT, (entityIds: Array<number>) => { ... });\n//\n// Reported as one id array per run rather than per entity, since nearly everything moves nearly every run. No\n// position travels with the id: the transform is a SharedArrayBuffer block already on the main thread, so a\n// listener reads it off `entity.components.transform`.\nexport const POSITION_UPDATED_EVENT = 'position-updated';\n\n// What a game asks createPhysicsUpdate for on top of plain movement.\nexport interface PhysicsUpdateOptions<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> {\n\t// Run for each entity that moved into another, straight after its own move. Optional: an update with no\n\t// callback still sweeps and comes to rest against what is in the way, it just decides nothing about it.\n\tonCollision?: CollisionFunction<C, T, W>\n\t// Extra components to hand the update, and so the callback, beyond transform and velocity. List a health\n\t// component here to take damage on collision. Optional per entity: one without it is still moved and collided.\n\toptional?: Array<keyof C & string>\n}\n\n// What PhysicsSystem needs to know about an update to set up its components and queries. Stamped on by\n// createPhysicsUpdate so a game declares its collision setup once, in the module both backends import.\nexport interface PhysicsUpdateMetadata<C extends ComponentMap> {\n\t// Whether the update needs the collidable query. Always true for createPhysicsUpdate, which always sweeps;\n\t// the bare physicsUpdate carries no metadata and never asks for it.\n\tcollision: boolean\n\toptional: Array<keyof C & string>\n}\n\nexport type PhysicsUpdateFunction<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> = EntityUpdateFunction<C, T, W> & {\n\tphysics: PhysicsUpdateMetadata<C>\n};\n\n// Builds the update a game runs physics through when it wants its entities to notice each other. The result is\n// a plain EntityUpdateFunction that goes to both backends: createComponentWorker in the worker file, and\n// PhysicsSystem's `updateFunction` for the in-process fallback.\n//\n// export const gamePhysicsUpdate = createPhysicsUpdate<Components, GameUpdateComponents>({\n// optional: ['health'],\n// onCollision(world, a, b, queries, callbacks) { ... },\n// });\n//\n// Every update built here sweeps, so an entity comes to rest against what is in its way. The callback is on top\n// and optional; movement that walks through everything is the bare physicsUpdate below. The callback is baked\n// in rather than passed to PhysicsSystem because functions do not survive postMessage to the worker.\nexport function createPhysicsUpdate<\n\t// Constrained to a map holding the physics components because `T` is the game's components narrowed to\n\t// transform and velocity; every game already spreads physicsRegistry into its own, as PhysicsSystem requires.\n\tC extends ComponentMap & PhysicsComponents = PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n>(options: PhysicsUpdateOptions<C, T, W> = {}): PhysicsUpdateFunction<C, T, W> {\n\tconst { onCollision, optional = [] } = options;\n\n\t// Built by preRun, read by each entity update after it. Safe as a closure variable because a run is one\n\t// unbroken pass - preRun, then every entity - never re-entered part way through.\n\tlet broadphase: CollisionBroadphase<T> | undefined;\n\n\tconst update: PhysicsUpdateFunction<C, T, W> = Object.assign(\n\t\t(world: W, entityId: number, components: T, queries: EntityQueryComponents<C>, callbacks: ComponentSystemCallbacks<C>) => {\n\t\t\t// Killed earlier this run\n\t\t\tif(components.entity?.[DEAD_INDEX] === 1) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst self: MovingEntity<T> = { entityId, components };\n\t\t\tconst interpolation = components.interpolation;\n\t\t\tconst seconds = world.elapsedTime / 1000;\n\t\t\tconst moveX = components.velocity[VELOCITY_X_INDEX] * seconds;\n\t\t\tconst moveY = components.velocity[VELOCITY_Y_INDEX] * seconds;\n\n\t\t\t// Taken before anything is written, so the segment a renderer blends along runs from this step's start\n\t\t\t// to its end.\n\t\t\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\n\t\t\t// No tree means this update was called by hand without preRun: nothing to sweep against, plain move.\n\t\t\t// Bound locally so it stays narrowed inside the overlap callback below.\n\t\t\tconst tree = broadphase;\n\t\t\tif(!tree) {\n\t\t\t\tmove(entityId, components.transform, moveX, moveY, callbacks, world.reportMoves);\n\t\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Native bounce turns an entity around on contact without a game callback; the bounciness component\n\t\t\t// decides whether and how much. Read before the move because it also decides how the move resolves.\n\t\t\tconst bouncing = components.bounciness !== undefined;\n\n\t\t\t// Resolved off the blocks and only written once known, so an entity is never inside another. The move\n\t\t\t// resolves with or without a callback - coming to rest is what physics does about a collision,\n\t\t\t// onCollision is what the game does. A non-bouncing entity slides along a single axis off a corner; a\n\t\t\t// bouncing one does not, since it is about to turn around off the face it hit.\n\t\t\tconst moved = tree.resolveMove(self, moveX, moveY, !bouncing);\n\t\t\tmove(entityId, components.transform, moved.moveX, moved.moveY, callbacks, world.reportMoves);\n\t\t\t// Before any callback, so the stamp covers exactly the pair physics produced. A callback that writes\n\t\t\t// the transform is a game move, blended towards next frame like a teleport.\n\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\t// A non-bouncing entity still has to look, since what it ran into may bounce off it.\n\t\t\tif(!tree.hasBounciness && !onCollision) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Handled after the entity is put down, so both bounce and callback act on where it ended up; the\n\t\t\t// flipped velocity is applied by the next run. A contact is resolved once per run, by whichever side\n\t\t\t// reached it first - claimContact is what makes the second side's turn a no-op.\n\t\t\ttree.forEachOverlapping(self, other => {\n\t\t\t\tif(tree.claimContact(entityId, other.entityId)) {\n\t\t\t\t\tresolveContact(world, self, other, queries, callbacks, onCollision);\n\t\t\t\t}\n\t\t\t}, moved.moveX, moved.moveY);\n\n\t\t\t// Then what the move was stopped against, which the overlap check cannot find: the entity rests\n\t\t\t// touching it, not through it. The two lists never share an entry.\n\t\t\tfor(const other of moved.blocking) {\n\t\t\t\tif(tree.claimContact(entityId, other.entityId)) {\n\t\t\t\t\tresolveContact(world, self, other, queries, callbacks, onCollision);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\tphysics: {\n\t\t\t\t// The sweep needs the collidable query even with no callback, so this is not `onCollision !== undefined`.\n\t\t\t\tcollision: true,\n\t\t\t\toptional,\n\t\t\t},\n\t\t},\n\t);\n\n\t// preRun sees every entity at once, so it is the only place the tree can be built from one consistent moment,\n\t// before any of this run's movement.\n\tupdate.preRun = (world, entities, queries) => {\n\t\tbroadphase = new CollisionBroadphase<T>(queries[COLLIDABLE_QUERY] ?? [], world.elapsedTime / 1000);\n\t};\n\n\treturn update;\n}\n\n// Everything a contact means, applied once for the pair: both sides turned around by their own bounciness, then\n// the one callback. `self` is whichever side reached the contact first, so a game acting on only one of the two\n// would leave the other untouched half the time - the callback has to decide for both.\nfunction resolveContact<\n\tC extends ComponentMap,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld,\n>(\n\tworld: W,\n\tself: MovingEntity<T>,\n\tother: CollisionEntity<T>,\n\tqueries: EntityQueryComponents<C>,\n\tcallbacks: ComponentSystemCallbacks<C>,\n\tonCollision?: CollisionFunction<C, T, W>,\n): void {\n\tbouncePair(self, other);\n\tif(onCollision) {\n\t\tonCollision(world, self, other, queries, callbacks);\n\t}\n}\n\n// Movement only, and the only way to get movement with no collision detection: walks an entity straight through\n// anything in its way, for something that has no business noticing the world (a particle, a camera). For\n// entities stopped by each other, build the update with createPhysicsUpdate above, which sweeps around this move.\nexport function physicsUpdate<\n\tC extends ComponentMap & PhysicsComponents = PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n>(world: W, entityId: number, components: T, queries: EntityQueryComponents<C>, callbacks: ComponentSystemCallbacks<C>): void {\n\t// elapsedTime is ms, velocity is units per second.\n\tconst seconds = world.elapsedTime / 1000;\n\tconst interpolation = components.interpolation;\n\n\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\tmove(\n\t\tentityId,\n\t\tcomponents.transform,\n\t\tcomponents.velocity[VELOCITY_X_INDEX] * seconds,\n\t\tcomponents.velocity[VELOCITY_Y_INDEX] * seconds,\n\t\tcallbacks,\n\t\tworld.reportMoves,\n\t);\n\tfinishInterpolationStep(interpolation, world.tick);\n}\n\n// The half of publishing a step that happens before the move: where the entity stands now becomes the `prev`\n// the next frame blends out of, plus how much simulated time the segment covers. The duration is published\n// because a run does not always cover one step - a late run takes two at once - and a renderer dividing by the\n// fixed step would draw that at double speed. Nothing is written without the component, keeping it opt-in.\nfunction startInterpolationStep(interpolation: Float32Array | undefined, transform: Float32Array, elapsedTime: number): void {\n\tif(!interpolation) {\n\t\treturn;\n\t}\n\n\tinterpolation[INTERPOLATION_PREV_X_INDEX] = transform[TRANSFORM_X_INDEX];\n\tinterpolation[INTERPOLATION_PREV_Y_INDEX] = transform[TRANSFORM_Y_INDEX];\n\tinterpolation[INTERPOLATION_DURATION_INDEX] = elapsedTime;\n}\n\n// The half after the move. Written unconditionally, so an entity pressed against a wall still publishes a step\n// (with `prev` where it is) rather than going quiet and leaving a renderer blending against stale data. The\n// tick goes last through storeFloat32 - a release store, so a reader that sees it also sees the matching `prev`\n// and transform. That is the whole publication protocol; see INTERPOLATION_TICK_INDEX.\nfunction finishInterpolationStep(interpolation: Float32Array | undefined, tick: number): void {\n\tif(!interpolation) {\n\t\treturn;\n\t}\n\n\tstoreFloat32(interpolation, INTERPOLATION_TICK_INDEX, tick);\n}\n\n// Applies an already-decided move and reports where the entity ended up. The one place the position is written,\n// reached only once the final position is known. Applied with addAtomicFloat32 rather than `+=` because another\n// thread may be adding to the same shared position at once and a plain read-modify-write would drop one; the\n// `max` cap is unwanted, so Infinity leaves a plain atomic add. A move that went nowhere is not reported, which\n// keeps a world of still entities quiet. See PhysicsSystem for `reportMoves`.\nfunction move(entityId: number, transform: Float32Array, moveX: number, moveY: number, callbacks: PositionCallbacks, reportMoves?: boolean): void {\n\tif(moveX === 0 && moveY === 0) {\n\t\treturn;\n\t}\n\n\tif(moveX !== 0) {\n\t\taddAtomicFloat32(transform, TRANSFORM_X_INDEX, moveX, Infinity);\n\t}\n\tif(moveY !== 0) {\n\t\taddAtomicFloat32(transform, TRANSFORM_Y_INDEX, moveY, Infinity);\n\t}\n\n\tif(reportMoves === false) {\n\t\treturn;\n\t}\n\n\t// Just the id: the main thread reads the position off the shared block, so no copy is worth sending.\n\tcallbacks.emitSystemEvent(POSITION_UPDATED_EVENT, entityId);\n}\n\n// The one callback move reaches for, narrowed to the event physics reports.\ninterface PositionCallbacks {\n\temitSystemEvent(event: typeof POSITION_UPDATED_EVENT, entityId: number): void\n}\n\nexport default physicsUpdate;\n","import { ComponentSystem } from '@daneren2005/shared-memory-ecs';\nimport type { BaseEntity, BaseWorld, ComponentDefinitionMap, ComponentMap, ComponentSystemQuery, EntityUpdateComponents, EntityUpdateFunction, SystemConfig } from '@daneren2005/shared-memory-ecs';\nimport { startSpawnInterpolation, type SpawnableEntity } from '../components/interpolation-component';\nimport type { PhysicsComponents, PhysicsUpdateComponents } from '../components/registry';\nimport physicsUpdate, { POSITION_UPDATED_EVENT, type PhysicsUpdateMetadata, type PhysicsWorld } from './physics-update';\nimport { COLLIDABLE_QUERY } from './collision';\n\n// Default physics step, in ms. 20Hz rather than every frame: cheaper for no loss of correctness, only\n// smoothness, which InterpolationSystem restores. `deltaBetweenRuns: 0` runs it every frame, which a game with\n// very fast entities and no interpolation wants - a longer step is a longer move, and a move longer than an\n// obstacle is thick sweeps clean through it.\nexport const DEFAULT_PHYSICS_STEP_MS = 50;\n\nexport interface PhysicsSystemConfig<\n\tC extends ComponentMap & PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> extends Partial<SystemConfig> {\n\t// Worker entry point that runs the update off the main thread. Bundlers need the `new Worker(new URL(...))`\n\t// call written out in the game's own source, so the game supplies the file:\n\t//\n\t// getWorker: () => new Worker(new URL('./physics.worker.ts', import.meta.url), { type: 'module' })\n\t//\n\t// Left off, the simulation runs in-process on the main thread instead.\n\tgetWorker?: () => Worker\n\t// Forces the in-process worker even with a getWorker. Defaults to true when no getWorker was given.\n\tforceMainThread?: boolean\n\t// The update to run, defaulting to plain movement that walks through anything in its way. Build one with\n\t// createPhysicsUpdate for collisions and pass the same function the worker file hands to createComponentWorker\n\t// so both backends match; it stamps `optional` and the collidable query on, so they need not be repeated here.\n\tupdateFunction?: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> }\n\t// Extra components to send with each moving entity, for an update built by hand. Overrides what the update\n\t// function declared.\n\toptional?: Array<keyof C & string>\n\t// Whether to gather the collidable query for the update's collision detection. Defaults to what the update\n\t// declared: on for createPhysicsUpdate, off for the bare physicsUpdate.\n\tcollision?: boolean\n\t// Whether each run reports what moved through POSITION_UPDATED_EVENT. Defaults to whether anything is\n\t// listening, checked per run. Worth a setting because the worker pays for it - an id per moved entity, cloned\n\t// back across the boundary - so force it `true` only for a listener this system cannot see.\n\treportMoves?: boolean\n\t// Narrows which entities this system moves to a subset, for spreading one simulation across several workers:\n\t// give each the same update but a shard filter (`entity => entity.eid % workers === n`). The collidable query\n\t// and any extra `queries` are gathered whole regardless, so every shard sweeps against the whole world.\n\tfilter?: (entity: BaseEntity<C>) => boolean\n\t// Restricts this system to one group of the world - a solar system, a level - so it never sees, moves, or\n\t// collides against anything outside it. Unlike `filter` (which shards movers but still sweeps the whole world),\n\t// `scope` narrows the collidable set too, so an out-of-scope body never enters this system's broadphase and two\n\t// overlapping groups cannot collide across the boundary. Give each group its own PhysicsSystem with its own\n\t// `scope` (`entity => entity.components.group?.groupId === thisId`). Combined with `filter` an entity must pass\n\t// both to be moved. Extra `queries` are left whole - scope them in the caller if a group-local read is wanted.\n\tscope?: (entity: BaseEntity<C>) => boolean\n\t// Extra queries sent to the worker alongside the collidable one, for an update that reads more of the world\n\t// than the entity it moves (e.g. flocking). Read by name off `queries`, gathered whole regardless of `filter`,\n\t// and merged with the collidable query rather than replacing it.\n\tqueries?: { [key: string]: ComponentSystemQuery<C> }\n}\n\n// Moves every entity with a transform and a velocity, and (when the update detects collisions) reports overlaps\n// to the game's callback. Generic over `C` the game's component map, `T` the blocks its update touches, and `W`\n// the per-run data object.\nexport default class PhysicsSystem<\n\tC extends ComponentMap & PhysicsComponents,\n\tT extends PhysicsUpdateComponents & EntityUpdateComponents<C> = PhysicsUpdateComponents & EntityUpdateComponents<C>,\n\tW extends PhysicsWorld = PhysicsWorld,\n> extends ComponentSystem<C, T, W> {\n\t// Bumped per run and stamped onto every interpolated entity so a renderer can tell a blended position from one\n\t// physics just replaced. Only has to change; a Float32 holds integers to 2^24, over nine days at a 50ms step.\n\ttick = 0;\n\n\t// Undefined means \"whenever something is listening\", worked out per run by addDataToWorld.\n\treportMoves: boolean | undefined;\n\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: PhysicsSystemConfig<C, T, W> = {}) {\n\t\tconst updateFunction: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> } = options.updateFunction ?? physicsUpdate;\n\t\tconst optional = options.optional ?? updateFunction.physics?.optional ?? [];\n\t\tconst collision = options.collision ?? updateFunction.physics?.collision ?? false;\n\t\t// A moving entity reads its own body (collide category/mask) and bounciness before it searches, so both\n\t\t// travel with movers too - but only on the collision path, or they are blocks per mover for nothing.\n\t\t// Interpolation goes along unconditionally, since the update must publish into it and nothing flags which\n\t\t// entities have one. All are free for entities that lack them: the ECS only sends a block it actually holds.\n\t\tconst extraOptional: Array<keyof C & string> = [];\n\t\tif(collision && !optional.includes('body')) {\n\t\t\textraOptional.push('body');\n\t\t}\n\t\tif(collision && !optional.includes('bounciness')) {\n\t\t\textraOptional.push('bounciness');\n\t\t}\n\t\tif(!optional.includes('interpolation')) {\n\t\t\textraOptional.push('interpolation');\n\t\t}\n\t\tif(collision) {\n\t\t\textraOptional.push('entity');\n\t\t}\n\t\tconst movingOptional = extraOptional.length ? [...extraOptional, ...optional] : optional;\n\n\t\t// Collision is between anything with a transform and a body, not only movers, so a ship can hit a station.\n\t\t// Gathered only when something will read it, since it ships a block per entity in the world. Merged with\n\t\t// the game's own `queries`, and neither is narrowed by the shard `filter`.\n\t\t// `bounciness` has to be on this side too, not just the movers: a contact is resolved once and turns both\n\t\t// sides around by their own bounciness, and the broadphase decides from these blocks whether anything in the\n\t\t// run can bounce at all. Left off, hasBounciness is never true and nothing ever bounces.\n\t\tconst collidableOptional = optional.includes('bounciness') ? optional : ['bounciness', ...optional];\n\t\tconst collidableQuery = collision ? {\n\t\t\t[COLLIDABLE_QUERY]: {\n\t\t\t\trequired: ['transform', 'body'] as Array<keyof C>,\n\t\t\t\toptional: ['velocity', 'entity', ...collidableOptional] as Array<keyof C>,\n\t\t\t\t// Scope narrows the collidable set as well as the movers, so an out-of-scope body never enters the\n\t\t\t\t// broadphase; the shard `filter` deliberately does not, since every shard sweeps the whole group.\n\t\t\t\tfilter: options.scope,\n\t\t\t},\n\t\t} : undefined;\n\t\tconst queries = collidableQuery || options.queries ? { ...collidableQuery, ...options.queries } : undefined;\n\n\t\t// A moving entity must be in scope and in this shard: scope bounds the group, filter shards it across workers.\n\t\tconst moverFilter = combineFilters(options.scope, options.filter);\n\n\t\tsuper(world, {\n\t\t\tname: options.name ?? 'PhysicsSystem',\n\t\t\tdeltaBetweenRuns: options.deltaBetweenRuns ?? DEFAULT_PHYSICS_STEP_MS,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// Movement needs both; an entity with only one is not moved, though it can still be collided with.\n\t\t\trequired: ['transform', 'velocity'],\n\t\t\toptional: movingOptional,\n\t\t\tfilter: moverFilter,\n\t\t\tupdateFunction,\n\t\t\tqueries,\n\n\t\t\tforceMainThread: options.forceMainThread ?? !options.getWorker,\n\t\t\t// Only reached when a getWorker was supplied: without one forceMainThread defaults to true.\n\t\t\tgetWorker: options.getWorker ?? (() => {\n\t\t\t\tthrow new Error('PhysicsSystem cannot start a worker: no getWorker was supplied');\n\t\t\t}),\n\t\t});\n\n\t\tthis.reportMoves = options.reportMoves;\n\t}\n\n\t// Seeds a just-spawned, already-moving entity's interpolation so a renderer draws it leaving its spawn point\n\t// from the next frame rather than parked there until this system's first step reaches it.\n\tstartInterpolation(entity: SpawnableEntity): void {\n\t\tconst step = this.deltaBetweenRuns;\n\t\tif(step <= 0) {\n\t\t\treturn;\n\t\t}\n\n\t\tstartSpawnInterpolation(entity, {\n\t\t\tstepMs: step,\n\t\t\tstepFraction: this.currentDelta / step,\n\t\t\ttick: this.tick,\n\t\t});\n\t}\n\n\t// Stamps the run with its step number and whether to report what moved. A subclass overriding this to attach\n\t// its own data must call `super.addDataToWorld(world)`, or `tick` stays undefined and every interpolated\n\t// entity looks to a renderer like it has never had a physics step.\n\taddDataToWorld(world: W): void {\n\t\tworld.tick = ++this.tick;\n\t\t// Asked per run so a game can attach and drop the listener freely.\n\t\tworld.reportMoves = this.reportMoves ?? this.listenerCount(POSITION_UPDATED_EVENT) > 0;\n\t}\n}\n\n// Ands two optional entity filters. Either alone is handed straight back; only both present pays for a closure.\nfunction combineFilters<C extends ComponentMap & PhysicsComponents>(\n\ta: ((entity: BaseEntity<C>) => boolean) | undefined,\n\tb: ((entity: BaseEntity<C>) => boolean) | undefined,\n): ((entity: BaseEntity<C>) => boolean) | undefined {\n\tif(!a) {\n\t\treturn b;\n\t}\n\tif(!b) {\n\t\treturn a;\n\t}\n\n\treturn entity => a(entity) && b(entity);\n}\n","import { loadFloat32 } from '@daneren2005/shared-memory-objects/utils/float32-atomics';\nimport type { EntityUpdateFunction } from '@daneren2005/shared-memory-ecs';\nimport type { InterpolationComponents, InterpolationUpdateComponents } from '../components/registry';\nimport {\n\tINTERPOLATION_DURATION_INDEX,\n\tINTERPOLATION_PREV_X_INDEX,\n\tINTERPOLATION_PREV_Y_INDEX,\n\tINTERPOLATION_PROGRESS_INDEX,\n\tINTERPOLATION_SYNCED_TICK_INDEX,\n\tINTERPOLATION_TICK_INDEX,\n\tINTERPOLATION_X_INDEX,\n\tINTERPOLATION_Y_INDEX,\n} from '../components/interpolation-component';\nimport { TRANSFORM_X_INDEX, TRANSFORM_Y_INDEX } from '../components/transform-component';\n\n// Walks the render position along the segment between where the entity was before the last physics step and\n// where it is after it: `render = prev + (current - prev) * progress`.\n//\n// Every position it writes is one the simulation actually produced, so an entity is drawn easing into a wall or\n// turning a corner because that is what happened - not guessed forward from velocity and snapped back. The cost\n// is one fixed step of latency, always, in exchange for never being wrong.\n//\n// Two rules cover the pacing: advance by `elapsedTime / duration` of the segment each frame, and restart on the\n// next step when it lands, dropping whatever was left of the old segment. None of it is read off the physics\n// system's accumulator, which resets when a run is posted, not when it lands - different moments on a worker\n// thread, and the source of a stutter whenever a run takes longer than a frame. Instead the segment's `duration`\n// is published with it, so a late run just banks more time and the next segment covers it; a slow run costs\n// latency and nothing else.\nexport const interpolationUpdate: EntityUpdateFunction<InterpolationComponents, InterpolationUpdateComponents> = (world, entityId, components) => {\n\tconst interpolation = components.interpolation;\n\tconst transform = components.transform;\n\n\t// The stamp is read on both sides of everything it publishes: physics may be moving this entity on another\n\t// thread, and a `prev` from after the move with a transform from before draws it walking backwards.\n\tconst before = loadFloat32(interpolation, INTERPOLATION_TICK_INDEX);\n\tconst prevX = interpolation[INTERPOLATION_PREV_X_INDEX];\n\tconst prevY = interpolation[INTERPOLATION_PREV_Y_INDEX];\n\tconst duration = interpolation[INTERPOLATION_DURATION_INDEX];\n\tconst x = transform[TRANSFORM_X_INDEX];\n\tconst y = transform[TRANSFORM_Y_INDEX];\n\tconst after = loadFloat32(interpolation, INTERPOLATION_TICK_INDEX);\n\n\t// Caught mid-step, so the two ends do not belong together. Leave the render position for this one frame\n\t// rather than drawing from a mismatched pair - a 16ms hold is invisible, a jump is not.\n\tif(before !== after) {\n\t\treturn;\n\t}\n\n\t// No segment to be part way along: an entity physics never moved, or one whose first step has not landed.\n\t// Progress is left finished, not zero, so the first real step starts from the right place - the rule below\n\t// takes a whole segment off whatever was banked, and taking it off zero would open with unpayable debt.\n\tif(duration <= 0) {\n\t\tinterpolation[INTERPOLATION_PROGRESS_INDEX] = 1;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t\tinterpolation[INTERPOLATION_X_INDEX] = x;\n\t\tinterpolation[INTERPOLATION_Y_INDEX] = y;\n\n\t\treturn;\n\t}\n\n\tlet progress = interpolation[INTERPOLATION_PROGRESS_INDEX];\n\tif(after !== interpolation[INTERPOLATION_SYNCED_TICK_INDEX]) {\n\t\t// A step landed: restart at its beginning, which is where the entity was just drawn anyway (the new\n\t\t// segment's `prev` is the old one's end). Whatever was left undrawn is dropped rather than carried -\n\t\t// carrying it could only be a deficit, which pins the entity at `prev` and never gets paid off.\n\t\tprogress = 0;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t}\n\n\t// Capped at the segment end, which absorbs a slow physics run: the entity waits at the newest real position\n\t// and the wait is dropped, not banked, so a consistently-late worker costs latency and nothing else.\n\tprogress += world.elapsedTime / duration;\n\tif(progress > 1) {\n\t\tprogress = 1;\n\t}\n\tinterpolation[INTERPOLATION_PROGRESS_INDEX] = progress;\n\n\tinterpolation[INTERPOLATION_X_INDEX] = prevX + (x - prevX) * progress;\n\tinterpolation[INTERPOLATION_Y_INDEX] = prevY + (y - prevY) * progress;\n};\n\nexport default interpolationUpdate;\n","import { ComponentSystem } from '@daneren2005/shared-memory-ecs';\nimport type { BaseWorld, ComponentDefinitionMap, ComponentMap, EntityUpdateComponents, SystemConfig } from '@daneren2005/shared-memory-ecs';\nimport type { InterpolationComponents, InterpolationUpdateComponents } from '../components/registry';\nimport interpolationUpdate from './interpolation-update';\n\nexport interface InterpolationSystemConfig extends Partial<SystemConfig> {\n\t// Defaulted the opposite way to PhysicsSystem - main thread unless asked otherwise - since a render position\n\t// read a frame after it was computed is a frame stale, the latency this system exists to cut.\n\tforceMainThread?: boolean\n\tgetWorker?: () => Worker\n}\n\n// Fills in a render position for every entity with an `interpolation` component, once per frame, by blending\n// between the two positions physics published either side of its last step.\n//\n// It is deliberately not wired to the physics system: everything it needs is in the block, published by\n// whichever run wrote it. Pacing off the physics system would pace off when a run was posted, which on a worker\n// is not when its results arrive; see interpolation-update.ts. So it takes no configuration and follows a step\n// the game retunes mid-flight. Ordering after the physics system is a preference worth one frame of latency;\n// pause and `timeScale` need no code, since BaseWorld#runUpdate already skips and scales for free.\nexport default class InterpolationSystem<\n\tC extends ComponentMap & InterpolationComponents,\n> extends ComponentSystem<C, InterpolationUpdateComponents & EntityUpdateComponents<C>> {\n\tconstructor(world: BaseWorld<ComponentDefinitionMap, C>, options: InterpolationSystemConfig = {}) {\n\t\tsuper(world, {\n\t\t\tname: options.name ?? 'InterpolationSystem',\n\t\t\t// Every frame, which is the whole point.\n\t\t\tdeltaBetweenRuns: 0,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// No velocity is read - the blend is between two positions the simulation produced - so a velocity-less\n\t\t\t// entity is in this system on the same terms as a ship.\n\t\t\trequired: ['transform', 'interpolation'],\n\t\t\tupdateFunction: interpolationUpdate,\n\n\t\t\tforceMainThread: options.forceMainThread ?? !options.getWorker,\n\t\t\tgetWorker: options.getWorker ?? (() => {\n\t\t\t\tthrow new Error('InterpolationSystem cannot start a worker: no getWorker was supplied');\n\t\t\t}),\n\t\t});\n\t}\n}\n"],"mappings":"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"}