@daneren2005/shared-memory-physics 1.1.0 → 1.2.1

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/interpolation-component.ts","../src/components/registry.ts","../src/math/shapes.ts","../src/systems/collision.ts","../src/systems/spatial-index.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 in the world, how big it is, and which way it is facing. This is 2D physics, so the\n// transform is a position, the box that sits around it, and the angle that box is rotated to.\nexport interface TransformComponent {\n\tindex: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n}\n\n// How big an entity is and which way it starts out facing. This is the defining half of a transform: it is\n// what a game's entity template says, the same for every ship of a type, and it is what gives an entity a\n// transform at all - a config with neither a width nor a height does not load one.\n//\n// `width`/`height` are the *unrotated* size of the box, and are required: a box with no area never overlaps\n// anything, so an entity without a real size could never collide.\n//\n// `angle` is in radians, measured counter-clockwise from the +x axis, so it feeds Math.cos / Math.sin\n// directly. Nothing in this library writes it - it is the game's to set, usually from its heading - which is\n// why it is a starting facing here rather than saved state.\n//\n// A round entity can give a `radius` **instead of** a width and a height, which loads as a width and a height\n// of its diameter - so there is still only one size in the block however the config spelled it. Giving both\n// throws rather than picking a winner.\nexport interface TransformConfig {\n\twidth: number\n\theight: number\n\tangle?: number\n\tradius?: number\n}\n// Where the entity is, which is the half that is live runtime state: the physics system moves it every run,\n// so it is what has to round-trip through `save` for a saved world to resume where it left off. The size and\n// facing come back from the game's own template rather than the save.\n//\n// `x`/`y` are the *centre* of the box, not a corner: the box is rotated about that point and collision\n// projects out from it in both directions.\nexport interface TransformSerialization {\n\tx: number\n\ty: number\n}\n\n// Indexes into the backing Float32Array block. The physics update reads the same offsets off the raw shared\n// block, so they are exported for it (and for any game system that touches the block directly).\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 is what marks a config as having a transform at all. x/y are not in the list: they come back off\n\t// a save rather than out of a game's entity template, so on their own they do not describe an entity that\n\t// has a place in the world.\n\tloadProperties: ['width', 'height', 'radius'],\n\tload(entity, memory, config) {\n\t\t// A radius is the same size said differently, so it is turned into a width and a height here and nothing\n\t\t// downstream has to know which way the config was written. Both at once is a config that cannot be\n\t\t// honoured either way round, so it is refused rather than silently resolved.\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 is moving, in world units per second, along each axis. An entity with both a\n// transform and a velocity is moved by the physics system every run.\nexport interface VelocityComponent {\n\tindex: number\n\tvelocityX: number\n\tvelocityY: number\n}\n\n// Velocity is named per-axis rather than plain `x`/`y` because entity configs are flat and shared across\n// every component - `x`/`y` already belong to the transform. Either axis on its own is enough to give an\n// 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// Velocity is both the config an entity spawns with and live runtime state (a game's own systems apply\n// forces to it), so the whole thing round-trips back out through `save`.\nexport type VelocitySerialization = VelocityConfig;\n\n// Indexes into the backing Float32Array block. The physics update reads the same offsets off the raw shared\n// block, so they are exported for it (and for any game system that touches the block directly).\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 is willing to collide *with*. Only entities with a body take part\n// in collisions at all - but a body is loaded for anything with a size, so in practice the way to opt an entity\n// out is `collideCategory: 0` rather than leaving the component off.\n//\n// The categories themselves are the game's to name. This library only ever reads them as bits, so a game\n// declares whatever set it needs (ground, air, projectile, terrain, ...) and hands the numbers in through the\n// config.\nexport interface BodyComponent {\n\tindex: number\n\tshape: number\n\tcollideCategory: number\n\tcollideMask: number\n}\n\n// The shapes a body can be. All three are held in the same transform - a position, a width, a height and an\n// angle - so which one an entity is changes what that data means rather than where it lives:\n//\n// SHAPE_RECTANGLE - a width x height box turned to `angle`\n// SHAPE_CIRCLE - a circle of diameter `width`, which `angle` does not affect\n// SHAPE_CAPSULE - a capsule `width` from end to end and `height` thick, lying along the way it faces\n//\n// See src/math/shapes.ts for what each one means geometrically.\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// What a body collides as when the game does not say: a single bit, so a world where nothing sets a category\n// behaves exactly as it did before categories existed.\nexport const DEFAULT_COLLIDE_CATEGORY = 1;\n// ...and what it is willing to collide with: every bit, so a default body meets anything whose own mask lets\n// it. 0xFFFFFFFF does not fit in a signed int, but it is stored in a Uint32Array and JS bitwise operators\n// work on the same 32 bits, so it ANDs correctly against any category a game can supply.\nexport const DEFAULT_COLLIDE_MASK = 0xFFFFFFFF;\n\n// Whether two bodies collide: each side's mask has to accept the other's category.\n//\n// The test is deliberately **symmetric**, so a pair either collides or does not regardless of which one moved\n// into which. That matters because collisions are reported per entity as each one moves: a rule that depended\n// on the direction would make the outcome depend on the order the system happened to move them in. A game\n// that wants a one-sided *response* - a projectile that hurts a unit without the unit hurting it back - gets\n// that by checking `other` inside its own onCollision, not by the two entities disagreeing about whether they\n// touched.\n//\n// So a ground unit that should be hit by projectiles has to name projectiles in its own mask, not just the\n// other way round. `collideCategory: 0` collides with nothing at all, since every mask ANDs to 0 against it.\n//\n// `&` yields a signed 32 bit result, so a mask with the top bit set comes back negative - compare against 0\n// rather than testing for a positive number.\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 is what loads a body, the same way it is what loads a transform: anything big enough to be in the\n// world is collidable by default, and the collide properties are there to narrow that rather than to grant it.\n// They are all *defining* config out of a game's entity template, which is why there is no `save` below - a\n// reloaded entity gets its category and mask back from the template, not from the save.\nexport interface BodyConfig {\n\t// Not read by the loader, only listed in `loadProperties`: they are what marks a config as describing\n\t// something that has a place in the world, and so something that can be run into.\n\twidth?: number\n\theight?: number\n\n\t// One of the SHAPE_ constants above. Defaults to a circle for a config given a `radius` and a rectangle\n\t// otherwise, so only capsules have to name their shape.\n\tshape?: number\n\t// Not read here either - the transform is what turns it into a width and a height - but a config that gives\n\t// a radius is describing a circle, and that is what decides the default shape above.\n\tradius?: number\n\t// The bit(s) this entity collides as, and the bits it is willing to collide with. See canCollide.\n\tcollideCategory?: number\n\tcollideMask?: number\n}\n\n// Indexes into the backing Uint32Array block. The collision broadphase reads the same offsets off the raw\n// shared block, so they are exported for it (and for any game system that touches the block directly).\nexport const BODY_SHAPE_INDEX = 0;\nexport const BODY_CATEGORY_INDEX = 1;\nexport const BODY_MASK_INDEX = 2;\nexport const BODY_SIZE = 3;\n\nexport const bodyDefinition: ComponentDefinition<BodyComponent, Uint32Array, BodyConfig> = {\n\ttype: Uint32Array,\n\tsize: BODY_SIZE,\n\tloadProperties: ['width', 'height', 'radius', 'shape', 'collideCategory', 'collideMask'],\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]);\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};\n\t},\n};\n\n// An unknown shape is thrown on rather than defaulted: silently treating it as a rectangle would leave an\n// entity colliding with the wrong outline, which is far harder to spot than a config that fails to load.\n//\n// A config that says nothing is read off the size it gave instead - a `radius` describes a circle, and anything\n// else is a rectangle - so a game only has to name a shape for the one case its size cannot imply.\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';\nimport type { TransformComponent } from './transform-component';\n\n// Where an entity should be *drawn*, which stops being the same thing as where it is the moment physics runs\n// less often than the screen refreshes. A 50ms step against a 16ms frame means the transform only changes on\n// one frame in three, so an entity drawn straight off it visibly stutters; this block holds a position that\n// changes every frame instead.\n//\n// It is filled by InterpolationSystem by blending between the position the entity had before the last physics\n// step and the one it has after it, so **every position in here is one the simulation actually produced** -\n// somewhere on the segment between two real positions, never a guess past the end of one. The cost of that is\n// one step of latency: what is drawn is where the entity was a step ago, not where it is now.\n//\n// Nothing in the simulation ever reads this back. Collision, the sweep and the spatial index all work off the\n// transform, so a render position can never feed back into where an entity is allowed to go - which is also\n// what keeps it safe under lockstep multiplayer, where two machines running at different frame rates would\n// otherwise read different numbers out of the same tick.\nexport interface InterpolationComponent {\n\tindex: number\n\t// The render position. Rewritten every frame by InterpolationSystem - a game reads it and does not write\n\t// it, except through `snapEntity` after teleporting something.\n\tx: number\n\ty: number\n\t// Where the entity stood before the physics step now being blended. Written by the physics update.\n\tprevX: number\n\tprevY: number\n}\n\n// `interpolate` is what gives an entity this component at all, the way a size is what gives it a transform. It\n// is opt-in per entity rather than automatic on anything with a transform, because it is 20 bytes and a\n// per-frame visit for something a game may never draw.\n//\n// `x`/`y` are read off the same flat config the transform loads from, so a new entity's render position starts\n// where it spawned rather than sliding in from the origin on its first frame.\nexport interface InterpolationConfig {\n\tinterpolate?: boolean\n\tx?: number\n\ty?: number\n}\n\n// Indexes into the backing Float32Array block. The physics update and the interpolation update both read\n// these offsets off the raw shared block, so they are exported for them (and for any game system that wants\n// the render position without going through the accessors).\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, as a fraction of it: 0 at `prev`, 1 at the transform.\n// Written only by the interpolation system.\n//\n// Kept as a fraction rather than as milliseconds so that the leftover from one segment carries onto the next\n// one without needing to remember how long the old segment was - which matters because they are not all the\n// same length (see INTERPOLATION_DURATION_INDEX). It is allowed to go slightly **negative**, which is what\n// stops the drawn speed rippling: the frames that pass between two steps add up to a frame more or less than\n// the step covers, and carrying the difference forward instead of clamping it away is what keeps every frame's\n// worth of drawn movement identical.\nexport const INTERPOLATION_PROGRESS_INDEX = 4;\n// The step the interpolation system last reconciled against, so it can tell a step it has already started\n// drawing from one physics has just published. Written only by the interpolation system.\nexport const INTERPOLATION_SYNCED_TICK_INDEX = 5;\n// How much simulated time this segment covers, in milliseconds - the `elapsedTime` of the run that produced it.\n// Written by the physics update.\n//\n// Nearly always the physics step, and *not* assumed to be: a run that comes back late leaves more than a step's\n// worth of time banked, and the next one covers two steps in one go. Dividing that segment by the step rather\n// than by what it really covers would draw it at double speed, which is a stutter in exactly the case - physics\n// falling behind - where this component is supposed to be hiding one.\nexport const INTERPOLATION_DURATION_INDEX = 6;\n// The physics step this block's `prev` belongs to, written **last** by the physics update with a release store.\n// It is a publication stamp rather than anything a reader does arithmetic with: physics writes `prev` and then\n// the transform, on another thread, while the main thread is reading both - so a reader that sees the same tick\n// on either side of its own reads knows the two it got belong together. Reading them from either side of a\n// step would blend towards a position the entity has already left, which is the one mismatch that draws an\n// entity moving backwards.\n//\n// It is also the *arrival* signal, which is the whole reason the pacing lives in this block rather than being\n// read off the physics system's accumulator. That accumulator resets when a run is **posted**; these three\n// fields change when it **lands**, and on a worker those are not the same moment.\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// Seeded with the spawn position on both sides of the blend, and with the pacing at rest - so an entity\n\t\t// added between physics steps is drawn standing still where it was put rather than sliding in from\n\t\t// wherever the block happened to be last used. PhysicsSystem's first run stamps tick 1, so the 0 here is\n\t\t// what makes that first step read as a new one to reconcile against.\n\t\t//\n\t\t// A duration of 0 reads as \"no segment yet\", which draws the entity at its transform - which is also what\n\t\t// something the physics system never moves is left on for its whole life.\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 in here is derived from the transform and rebuilt on the next physics step, and\n\t// `interpolate` itself is defining config that comes back off the game's own entity template.\n};\n\n// Anything with the two components this reads, which is any entity from a world that registered\n// `physicsRegistry` - written structurally so it does not need the game's component map to say so.\nexport interface SnappableEntity {\n\tcomponents: {\n\t\ttransform?: TransformComponent\n\t\tinterpolation?: InterpolationComponent\n\t}\n}\n\n// Throws away the segment an entity is part way along and puts it, drawn and simulated, at the same place.\n//\n// **Call this after writing a transform directly.** Interpolation blends from where an entity was towards\n// where it is, so a game that teleports something by assigning `transform.x` has not moved it - it has made the\n// segment 500 units long, and the entity is drawn sliding the whole way across the map over the next step. This\n// is the one thing blending between real positions cannot work out for itself, because a long move and a\n// teleport are the same two numbers.\n//\n// It is not needed for anything physics did: a velocity change, a collision, an entity coming to rest against a\n// wall are all steps the simulation took and are all 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 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 so its world\n// allocates their memory pools alongside its game specific components:\n//\n// const registry = { ...physicsRegistry, health: healthDefinition };\n// const world = new BaseWorld(registry);\n//\n// The names here are what the physics systems query on, so they must be kept as the registry keys.\nexport const physicsRegistry = {\n\ttransform: transformDefinition,\n\tvelocity: velocityDefinition,\n\tbody: bodyDefinition,\n\tinterpolation: interpolationDefinition,\n};\n\n// The slice of a game's component map that the physics systems need. Declared as a type alias (not an\n// interface) so it satisfies the ECS's `ComponentMap` index signature. Systems here are generic over the\n// game's full component map and only require that it extends this.\nexport type PhysicsComponents = {\n\ttransform: TransformComponent\n\tvelocity: VelocityComponent\n\tbody: BodyComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The slice InterpolationSystem needs, which is a strictly smaller one: it never looks at a velocity or a body.\n// Split out so a game can be generic over it on its own, and so the interpolation update declares exactly what\n// it touches rather than inheriting the physics list.\nexport type InterpolationComponents = {\n\ttransform: TransformComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The same components as their concrete backing arrays, which is the form a system's update function sees them\n// in on the worker thread. `body` is optional here because it is optional on the query that moves entities -\n// movement does not need it, and it is only sent along when something is going to collide. The collidable\n// query requires it, so CollisionComponents narrows it back to guaranteed for the entities found through that.\n//\n// A game that hands extra components to its collision callback widens this with its own:\n//\n// type GameUpdateComponents = PhysicsUpdateComponents & { health?: Float32Array };\nexport type PhysicsUpdateComponents = {\n\ttransform: Float32Array\n\tvelocity: Float32Array\n\tbody?: Uint32Array\n\t// Optional in the same way and for the same reason: an entity a game never draws interpolated does not have\n\t// the component, and the update writes nothing for it.\n\tinterpolation?: Float32Array\n};\n\n// The blocks the interpolation update works on. Both are required - the system's query asks for both - which\n// is what lets the update read them without a guard per entity per frame.\nexport type InterpolationUpdateComponents = {\n\ttransform: Float32Array\n\tinterpolation: Float32Array\n};\n","// Geometry for the shapes a transform plus a body describe, all of them centred on (x, y) and turned `angle`\n// radians counter-clockwise. Everything here is pure number math on loose arguments rather than objects, so\n// the collision sweep can call it per pair without allocating.\n//\n// The three shapes are one idea in three settings: **an oriented core, grown by a radius**.\n//\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//\n// A circle is just a capsule whose segment has no length, so there are only two kinds of core - a box and a\n// segment - and therefore only three pair tests rather than one per combination of shapes. See shapesOverlap.\n\nimport { SHAPE_CAPSULE, SHAPE_CIRCLE, SHAPE_RECTANGLE } from '../components/body-component';\n\n// How far a capsule's core segment reaches from the centre, along the direction it faces. The caps are part\n// of the length, so a 40 x 10 capsule is 40 from end to end: 15 of segment either way plus a 5 radius cap.\n//\n// Clamped at 0, so a capsule told to be shorter than it is wide collapses to a circle of its own width rather\n// than turning itself inside out.\nexport function capsuleHalfLength(width: number, height: number): number {\n\treturn Math.max(0, (width - height) / 2);\n}\n\n// The radius of a shape's cap: half the diameter for a circle, half the thickness for a capsule, and nothing\n// at all for a rectangle, whose outline is its core.\n//\n// A circle takes its diameter from `width` and ignores `height` - loading one from a `radius` config sets both\n// to the same thing, so the two only ever disagree if a game writes the block itself.\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// Whether a shape encloses no area at all, and so can never overlap anything - not even something sitting\n// exactly on top of it. This is what keeps an entity the game never gave a size out of collisions, and which\n// of the two sizes matters depends on the shape: a circle is nothing without its width, while a capsule with\n// no thickness is a bare line however long it is.\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 width of the *axis aligned* box that fully contains a shape, whatever it is turned to - the box the\n// broadphase indexes it under, so it has to hold the whole shape or the tree would rule out its own candidates.\n//\n// A capsule always fits inside the rectangle its width and height describe, being that rectangle with its ends\n// rounded off, and so does a circle loaded from a `radius` (whose width and height agree). A circle is its\n// *width* alone though, so one whose height was written smaller by hand is taller than that rectangle - which is\n// why this is asked per shape rather than measured off the rectangle and trusted for everything.\nexport function shapeHalfWidth(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\t// A circle covers the same ground whichever way it is turned.\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}\n// The same for the y axis: the capsule's segment leans the other way, and the circle is still a circle.\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.\n//\n// Because a circle is a capsule with no length, every pair falls into one of three cases, picked by a switch\n// over the two shape constants with no indirection in the way:\n//\n// rectangle + rectangle - two boxes with no radius between them, which is the separating axis test below\n// round + round - the gap between the two core segments has to be under the two radii added together\n// rectangle + round - the gap between the core segment and the box has to be under the one radius\n//\n// Touching exactly counts as *not* overlapping in all three, matching orientedBoxesOverlap: the comparisons\n// are strict, so a capsule whose cap grazes a wall has not hit it.\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// Overlapping is the same relation whichever way round it is asked, so the one mixed case is written once\n\t\t// 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\t// b is round either way from here, so take its core segment now.\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 along the direction it faces: a capsule's segment, and nothing\n// for a circle (a point) or a rectangle (whose core is the whole box, handled separately).\nfunction coreHalfLength(shape: number, width: number, height: number): number {\n\treturn shape === SHAPE_CAPSULE ? capsuleHalfLength(width, height) : 0;\n}\n\n// Half the width of the *axis aligned* box that fully contains a rotated one. Turning a rectangle widens the\n// footprint it needs: each of its two sides contributes however much of itself now points along the x axis.\n// At angle 0 this is just width / 2, and at a quarter turn it is height / 2.\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}\n// The same for the y axis, with the two sides' contributions swapped.\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: two convex shapes miss each other if and\n// only if some axis exists that their projections do not both cover. For a pair of boxes only four axes can\n// ever be that axis - the two each box's own sides face along - so testing those four settles it.\n//\n// Touching exactly counts as *not* overlapping: the projections meet but do not cross. A box with no area\n// never overlaps anything either, even sitting inside another one - it collapses to a point or a line, and\n// the theorem has nothing to separate. Together those are what keeps an entity the game never gave a size\n// out of collisions entirely.\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\t// Everything is measured relative to a's centre, so only the offset between the two centres matters.\n\tconst offsetX = bX - aX;\n\tconst offsetY = bY - aY;\n\n\t// a's own two axes, then b's. Each box's sides face along (cos, sin) and (-sin, cos) of its angle.\n\treturn !separatedAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n}\n\n// Whether the two boxes' shadows on the axis (axisX, axisY) fail to reach each other. A box's shadow reaches\n// out from its centre by however far its own two half-sides project onto the axis, so the pair is separated\n// when the gap between the centres along that axis is at least the two reaches added together.\n//\n// The axes passed in are always a box's own sides, which are unit vectors, so the dot products below are\n// already true distances and nothing needs normalising.\nfunction separatedAlong(\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): boolean {\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 Math.abs(offsetX * axisX + offsetY * axisY) >= aReach + bReach;\n}\n\n// The squared distance between two line segments, each given as its two end points. Squared because that is\n// all the callers need - comparing it against a squared radius avoids a square root per pair.\n//\n// This is what settles every pair of round shapes: two capsules are their two segments grown by a radius each,\n// so they meet exactly when the segments come within the two radii of one another. A circle is a segment whose\n// ends are the same point, which this handles as the degenerate case rather than as a shape of its own.\n//\n// Walks the usual closest-point-between-segments argument: solve for the closest points on the two infinite\n// lines, then clamp back into the segments, re-solving the other parameter each time a clamp moves one. The\n// parallel case has no single solution, so it pins one end and lets the clamping find the rest.\nexport function segmentSegmentDistanceSquared(\n\taX1: number, aY1: number, aX2: number, aY2: number,\n\tbX1: number, bY1: number, bX2: number, bY2: number,\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\t// Two points - which is two circles, the most common pair of all.\n\t\treturn offsetX * offsetX + offsetY * offsetY;\n\t} else if(aLength <= 0) {\n\t\t// A circle against a capsule: only where it lands along the capsule's segment matters.\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 exactly when the two segments are parallel, which is where pinning aParam at 0 comes in: every\n\t\t\t// point along one is the same distance from the other, so any of them is as good an answer.\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 the closest point on b, so where it sits along a 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\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// The squared distance between a line segment and a rotated box. This is what settles a rectangle against a\n// circle or a capsule: the round shape is its segment grown by a radius, so it reaches the box exactly when the\n// segment comes within that radius of it.\n//\n// Measured in the box's own frame, where it is axis aligned and the whole thing reduces to a segment against a\n// rectangle sitting at the origin. Two disjoint convex shapes always have their closest pair at a corner of\n// one against the other, so once the crossing case is out of the way the answer is the nearest of: each end of\n// the segment to the box, and each corner of the box to the segment.\nexport function segmentBoxDistanceSquared(\n\tx1: number, y1: number, x2: number, y2: number,\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\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\t// Both ends into the box's frame, so the box becomes the rectangle from (-halfWidth, -halfHeight) to\n\t// (halfWidth, halfHeight).\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// The crossing case has to be caught on its own: a segment straight through the middle of the box has both\n\t// its ends outside it and every corner some way off, so the nearest-pair search below would report a real\n\t// distance for two shapes that are already through each other.\n\tif(!segmentMissesBox(localX1, localY1, localX2, localY2, halfWidth, halfHeight)) {\n\t\treturn 0;\n\t}\n\n\treturn Math.min(\n\t\t// Each end of the segment against the box...\n\t\tpointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight),\n\t\tpointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight),\n\t\t// ...and each corner of the box against the segment, which is where a segment running past a box at an\n\t\t// angle comes closest to it.\n\t\tpointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t);\n}\n\n// The squared distance from a point to a line segment: how far along the segment the point falls, clamped to\n// its ends, and then the gap from there.\nexport function pointSegmentDistanceSquared(x: number, y: number, x1: number, y1: number, x2: number, y2: number): number {\n\tconst deltaX = x2 - x1;\n\tconst deltaY = y2 - y1;\n\tconst length = deltaX * deltaX + deltaY * deltaY;\n\n\t// A segment with no length is just its own start point.\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\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Whether an axis aligned box at the origin and a segment fail to reach each other, by the same separating axis\n// argument as orientedBoxesOverlap: the box's own two axes, plus the one the segment faces along. Touching\n// exactly counts as reaching, so a grazing segment is measured as 0 away rather than as a near miss.\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\t// The segment's own axis. Both of its ends project to the same place on the normal, so one is enough - and\n\t// for a segment with no length the normal is (0, 0), which never separates and correctly leaves the two box\n\t// axes above to decide whether the point is inside.\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// The squared distance from a point to an axis aligned box at the origin: clamp the point onto the box and\n// measure what is left over. 0 for a point inside it.\nfunction pointBoxDistanceSquared(x: number, y: number, halfWidth: number, halfHeight: number): 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\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 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_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 the moment of contact a sweep has to get before it settles for it, in world units. Small enough\n// that the gap it leaves an entity resting against another is far below anything a game would draw, and large\n// enough that the halving below reaches it in a handful of steps.\nconst CONTACT_TOLERANCE = 1e-4;\n// A cap on that halving, so a move over a huge distance cannot turn into an unbounded number of overlap tests.\n// Each one narrows the answer by half, so this reaches the tolerance above for any move up to about sixteen\n// hundred units - and only an entity that is actually stopping somewhere ever spends a single one of them.\nconst MAX_REFINEMENTS = 24;\n\n// The name PhysicsSystem registers its extra query under, and the key the broadphase reads it back out of.\n// It holds everything with a transform and a body rather than only the entities the system moves, so a moving\n// entity can still run into a station, a wall or anything else that has no velocity of its own.\nexport const COLLIDABLE_QUERY = 'collidable';\n\n// The blocks a collision callback is handed for the entity that was run into. `transform` and `body` are the\n// guarantees - they are what the collidable query requires - while `velocity` and everything the game listed\n// in `optional` are there only if that entity has them.\nexport type CollisionComponents<T extends PhysicsUpdateComponents> = Partial<T> & {\n\ttransform: Float32Array\n\tbody: Uint32Array\n};\n\n// The entity that did the running into: the one being updated, so everything the system asked for is there.\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 at all.\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. It is called on the same thread the physics update\n// runs on, so it must be a plain function over the raw blocks - no entities, no world - and it reaches the\n// main thread only through `callbacks` (which report a changed property, a death, or an entity to spawn).\n//\n// It is called *per entity*, right after that entity has moved: `self` is the one that just moved and `other`\n// is what it has ended up on top of. Two moving entities that run into each other therefore get one call\n// each, with the roles swapped, which is what lets a callback act on itself - bouncing, taking damage - and\n// leave the other side to its own call. An entity with no velocity is never `self`, since the system never\n// moves it, but it is still found as `other`.\n//\n// `queries` is passed straight through so a callback that needs more than the two entities in front of it can\n// still walk the full collidable list, e.g. to credit a third entity for the kill.\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 is actually allowed to make, and what stopped it there.\n//\n// `fraction` is how far along the requested move it got: 1 for a clear path, 0 for an entity already pressed up\n// against something, and whatever is in between for one that came to rest part way.\n//\n// `blocking` is what it came to rest *against*, which the overlap test at that resting place will not report -\n// the whole point of stopping there is that the two are touching rather than through each other. More than one\n// entry means it wedged between them at the same moment, not that it should have stopped at the first.\nexport interface SweepResult<T extends PhysicsUpdateComponents> {\n\tfraction: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// What `blocking` is for the sweeps that find nothing in the way, which is most of them: one shared empty array\n// rather than one per entity per run. Read-only to a caller by nature - there is nothing in it to iterate.\nconst NOTHING_BLOCKING: Array<never> = [];\n\n// Everything about the entity doing the searching, read out of its blocks once so that a sweep testing the same\n// pair a dozen times over does not read them a dozen times over. 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}\n\n// One category's worth of the broadphase: every collidable entity that collides *as* that category, and an\n// R-tree over just those. A searching entity only looks in the buckets its own mask accepts, so a projectile\n// that can only hit units never walks the tree the terrain is in.\n//\n// Bucketed on the whole category *value* rather than a bit at a time, so an entity that collides as two things\n// at once still lives in exactly one bucket and can never be 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, entity by entity as\n// each one moves, what it might have hit.\n//\n// Everything is indexed where it stood at the *start* of the run, because that is the only moment all of the\n// entities agree on - by the time the third entity moves, the first two have already gone somewhere else. To\n// stay a true superset of what can actually collide, each box is grown by however far that entity could\n// travel before the run is out. The growth goes both ways rather than along the heading: a collision\n// callback is free to turn an entity around before it has had its own move, and a box grown only forwards\n// would then be pointing the wrong way.\n//\n// The tree is split by collide category rather than being one index over everything, so most of what an entity\n// cannot hit is ruled out a whole subtree at a time instead of one candidate at a time. Nothing is left empty:\n// a category with no collidable entities has no bucket at all, which also means Flatbush is never asked to\n// build an index over zero items.\nexport default class CollisionBroadphase<T extends PhysicsUpdateComponents> {\n\tprivate buckets: Array<CategoryBucket<T>> = [];\n\n\tconstructor(entities: Array<{ entityId: number, components: EntityUpdateComponents }>, seconds: number) {\n\t\t// Boxes are collected per category first because Flatbush has to be told how many items it will hold up\n\t\t// front, and how many that is only becomes clear once 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 query blocks generically as ComponentTypedArray since it cannot know what any given\n\t\t\t// game registered; narrow them to the concrete arrays the definitions allocate here, once, so neither\n\t\t\t// the search below nor the game's callback needs a cast of its own.\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 ever name category 0, so an entity that collides as nothing can never be the other half\n\t\t\t// of a collision either - dropping it here is exact rather than an approximation, and it is what makes\n\t\t\t// `collideCategory: 0` a way to opt an entity out of collisions 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, so dropping it here is exact rather than an\n\t\t\t// approximation - and it keeps an entity the game never gave a size out of the tree entirely.\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\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// Runs `handle` for everything `self` is really overlapping, having just moved.\n\t//\n\t// The tree narrows the field to whatever is anywhere near, then each of those is put through the full\n\t// rotated box test against where the two entities are *now* - which for an entity that has not had its own\n\t// move yet is still where it started. That is the price of collisions happening as each entity moves\n\t// rather than all at the end, and it evens out: the pair is looked at again from the other side once that\n\t// entity moves too.\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 with where this entity has actually ended up, ungrown: every box in the tree already allows\n\t\t// for its own entity's move, so allowing for this one's a second time would only widen the net.\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 (moveX, moveY) `self` may take before it runs into something, so that an entity comes\n\t// to rest against what is in its way instead of ending up inside it.\n\t//\n\t// Everything the move lands on is collected first and then they are all asked together, which is what keeps\n\t// an entity from stopping inside the near one because the far one happened to be looked at first: there is\n\t// one answer for the whole move, and it is the earliest contact of the lot.\n\t//\n\t// Contact is halved in on rather than solved: three shapes at any rotation to one another have no single\n\t// formula for when a pair first touches, while the overlap test the narrowphase already has answers it for\n\t// any pair anywhere. The result is always taken from a position that tested clear, so wherever the entity\n\t// is put down it is genuinely not inside any of them.\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\t// Nothing to work out for an entity that is not going anywhere, or that does not collide at all.\n\t\tif(!searcher || distance === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\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\t// What the move would land on top of, asked where it *ends* rather than anywhere along the way. That is\n\t\t// what keeps a clear move - which is nearly every move - down to a single shape test per candidate and\n\t\t// nothing else: an entity only ever walks its own path once something is genuinely in the way of it.\n\t\t//\n\t\t// The trade is that a move long enough to carry an entity clean past something is not stopped by it: by\n\t\t// the time the move is out there is nothing left to land on. That takes a single run covering more\n\t\t// ground than the thing in the way is thick, which a fixed `deltaBetweenRuns` rules out.\n\t\tconst candidates: Array<CollisionEntity<T>> = [];\n\t\tthis.forEachCandidate(searcher, endX - halfWidth, endY - halfHeight, endX + halfWidth, endY + halfHeight, other => {\n\t\t\t// Not where the move ends up, so nothing this move has to stop for.\n\t\t\tif(!overlapsAt(searcher, endX, endY, other)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Already inside it before the move began - something the game put there, or another system pushed\n\t\t\t// it into. It cannot be what *this* move ran into, and blocking on it would pin the entity inside\n\t\t\t// it for good with no way back out, so the move is let through and the pair is left to the overlap\n\t\t\t// callback that follows it.\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\t\tif(candidates.length === 0) {\n\t\t\treturn { fraction: 1, blocking: NOTHING_BLOCKING };\n\t\t}\n\n\t\t// From here the entity is definitely stopping short of where it asked to go, and all that is left is\n\t\t// where. `clear` is a fraction of the move it is known to fit at and `blocked` one it is known not to -\n\t\t// the whole move to begin with, since landing there is what put these candidates in the list at all -\n\t\t// and halving closes the gap between the two.\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 position that was not clear: the entity has come to rest a hair\n\t\t// short of exactly there, so this is what it is up against.\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 up against something is left exactly where it is rather than crawling the last\n\t\t// fraction of a tolerance forwards every run, which would report 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` is allowed to collide with at all -\n\t// the tree search and the category rules, with no geometry beyond the boxes themselves.\n\t//\n\t// The collide masks are applied before any shape test rather than after: a pair that can never collide is\n\t// settled by two ANDs instead of a full separating-axis test.\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 for everything in the bucket rather than once per candidate:\n\t\t\t// they all collide as the same category, so either this entity's mask accepts the lot of them or it\n\t\t\t// accepts none of them.\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\t// Every collidable entity is in some bucket, including this one.\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, which does have to be per candidate: a bucket shares one category\n\t\t\t\t// but every entity in it 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\thandle(other);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Reads out what a searching entity needs to be measured by, or undefined for one that cannot collide at all.\nfunction toSearcher<T extends PhysicsUpdateComponents>(self: MovingEntity<T>): Searcher | undefined {\n\t// An entity with no body does not collide at all. It can still be moved by the system - the query that\n\t// moves entities only asks for the body, it does not require it.\n\tconst body = self.components.body;\n\tif(!body) {\n\t\treturn undefined;\n\t}\n\n\t// Willing to run into nothing at all, so there is no bucket worth searching. Note this only stops it being\n\t// the entity that *finds* a collision: something whose mask accepts it can still find this one, and that is\n\t// the 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};\n}\n\n// Whether the searching entity, put down at (x, y) rather than wherever its own block says it is, overlaps\n// `other`. Taking the position as arguments is what 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 - that is what says where something\n// is and how big it is - while the body refines the box it is filed under to the shape it actually is. Anything\n// else a game queried travels along untouched, so a filter can read it back out.\nexport type SpatialComponents = {\n\ttransform: Float32Array\n\tbody?: Uint32Array\n};\n\n// One entity as the index hands it back: the id it was given plus whatever blocks came with it.\nexport interface SpatialEntity<T extends SpatialComponents = SpatialComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// Which of the entities a query found are wanted. Run per candidate the tree turns up, so it is the place to\n// put whatever the index itself cannot know - the searcher's own id, which team something is on, whether it is\n// already dead - and everything it rejects is as if it were never in the box at all.\nexport type SpatialFilter<T extends SpatialComponents = SpatialComponents> = (entity: SpatialEntity<T>) => boolean;\n\n// An R-tree over a set of entities, for the questions a game asks about *where things are* rather than about\n// what is running into what: who is in this area, and what is the closest thing to this point.\n//\n// This is the same machinery the collision broadphase is built on, minus everything collision specific - there\n// are no collide categories, no room left for a move, and no shape tests. Every answer is in terms of the\n// axis aligned box each entity occupies, so an entity is \"found\" as soon as that box is reached, and picking\n// out the ones that really matter is the caller's to do with a `filter` (see SpatialFilter).\n//\n// Nothing here reads the blocks after the constructor, so the index is a snapshot: it describes where everything\n// was at the moment it was built. Build one per run - the entities it holds are moving, and a tree built last\n// run is a tree of where they all used to be.\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 index over nothing at all: Flatbush cannot be built to hold zero items, and there is no\n\t// answer to give from an empty index anyway.\n\tprivate index: Flatbush | undefined;\n\n\t// `entities` is anything shaped like a query result - `{ entityId, components }` - so a system's own\n\t// `queries.whatever` goes straight in. Entities without a transform are skipped rather than rejected: a\n\t// query that only *optionally* asks for one is a perfectly reasonable thing to hand over, and something with\n\t// no place in the world cannot be found by a search of the world.\n\tconstructor(entities: Iterable<{ entityId: number, components: EntityUpdateComponents }>) {\n\t\t// Boxes are collected up front because Flatbush has to be told how many items it will hold before any of\n\t\t// them are added, and how many that is only becomes clear once everything 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 query blocks generically as ComponentTypedArray since it cannot know what any given\n\t\t\t// game registered; narrow them to the concrete arrays the definitions allocate here, once, so neither\n\t\t\t// the searches below nor the game's filter needs a cast of its own.\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// A body only says which outline the transform's width and height describe, so an entity without one is\n\t\t\t// taken as the plain rectangle they already are. That keeps the index usable for anything with a place\n\t\t\t// in the world rather than only for 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, an entity with no area is kept: a search is a question about where\n\t\t\t// something is, and a thing with a position but no size still has an answer to it.\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 actually in the index, which is not how many were handed to it: anything without a\n\t// transform never made it in.\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 - two boxes that\n\t// share nothing but an edge are still found - so an exact question about where the edges fall is one for the\n\t// caller 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 rather than over its results is what keeps a rejected candidate from ever\n\t\t// reaching the results array in the first place.\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// The same search written from the middle out: everything within `reachX` / `reachY` of (x, y) on each axis.\n\t// This is the form most game searches are actually in - something looking a certain distance around itself -\n\t// and it saves every caller writing the same four sums.\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 there is none within `maxDistance`.\n\t//\n\t// This is a walk of the tree in distance order rather than a search followed by a sort: it settles on an\n\t// answer as soon as the nearest box is reached, and never measures the far side of the world at all. So a\n\t// game asking \"who is nearest\" should ask it this way rather than searching a box and sorting what comes\n\t// back - and `maxDistance` is what keeps it from walking the whole tree when the answer is that nobody is.\n\t//\n\t// Distance is to the entity's **box**, not to its centre, so a big thing is as near as its nearest edge -\n\t// which is what a ship closing on a station wants, and it makes `maxDistance` a range past the hull rather\n\t// than a range that a large enough target could sit outside of while overlapping the searcher.\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 - for a game\n\t// that wants a few to choose between rather than only the one.\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 that `maxResults` counts entities the game actually\n\t\t// wants: filtering after the fact would let a handful of rejects use up the whole allowance and return\n\t\t// nothing while a valid target sat just 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 { addAtomicFloat32, storeFloat32 } from '@daneren2005/shared-memory-objects';\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 CollisionFunction, type MovingEntity } from './collision';\n\n// The per-run data object every physics update is handed, which is the base one plus the step counter the\n// interpolation component is stamped with. PhysicsSystem fills `tick` in from `addDataToWorld`, so a game that\n// subclasses the system to add data of its own widens this rather than replacing it - and **must call\n// `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, counted up once per run. Only ever compared for equality: it is the\n\t// publication stamp the interpolation block is read through, not a clock.\n\ttick: number\n\t// Whether to report this run's moves through POSITION_UPDATED_EVENT. Set by PhysicsSystem from whether\n\t// anything is actually listening, because the cost of the event is paid in the **worker** - an id per moved\n\t// entity pushed into an array, and that array structured-cloned back across the boundary - long before a\n\t// listener would have got the chance not to exist. Undefined means report, so an update driven by hand\n\t// behaves as it always did.\n\treportMoves?: boolean\n}\n\n// The event a move is reported through, emitted on the **system** on the main thread once the run that moved\n// them completes, carrying the ids of everything that moved in that run:\n//\n// physicsSystem.on(POSITION_UPDATED_EVENT, (entityIds: Array<number>) => {\n// for(const eid of entityIds) { ... }\n// });\n//\n// Nearly every entity a physics system owns moves on nearly every run, which rules out reporting the moves\n// one entity at a time: an event object apiece in the worker, cloned across the boundary, then an eid lookup\n// and an emit apiece on the main thread costs more, at a few thousand entities, than moving them did. So a\n// run's moves are reported as one array of ids on the system instead - see emitSystemEvent in the ECS.\n//\n// No position travels with the id. The transform is a SharedArrayBuffer block, so where the entity ended up\n// is already on the main thread the moment the worker writes it; a listener reads it off\n// `entity.components.transform` (or straight off the block) rather than being handed a copy of what it has.\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 has moved into another one, straight after its own move. Optional: an update\n\t// with no callback still sweeps, so leaving it off asks for movement that comes to rest against whatever is\n\t// in the way and decides nothing about it.\n\tonCollision?: CollisionFunction<C, T, W>\n\t// Extra components to hand the update - and so the collision callback - beyond the transform and velocity\n\t// physics itself needs. A game that wants to take damage on collision lists its health component here so\n\t// that block travels to the worker alongside the transform. They are optional per entity: an entity that\n\t// does not have one is still moved, and still collides, it just arrives without that block.\n\toptional?: Array<keyof C & string>\n}\n\n// What PhysicsSystem needs to know about an update function to set up the same components and queries the\n// function expects. Stamped on by createPhysicsUpdate so a game declares its collision setup in the one\n// module both its worker file and its main thread system import, with no chance of the two drifting apart.\nexport interface PhysicsUpdateMetadata<C extends ComponentMap> {\n\t// Whether the update needs the collidable query - everything with a transform and a body, whether it moves\n\t// or not. Always true for an update built by createPhysicsUpdate, which always sweeps; the bare\n\t// physicsUpdate carries no metadata at all and so 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 function a game runs physics through when it wants its entities to notice each other.\n// The result is a plain EntityUpdateFunction, so it goes to both backends the same way the bare physicsUpdate\n// does - handed to createComponentWorker in the game's worker file, and to PhysicsSystem as its\n// `updateFunction` for the in-process fallback:\n//\n// // game-physics-update.ts - imported by both the worker file and the main thread system\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 always comes to rest against what is in its way rather than\n// ending up inside it. The callback is on top of that and is optional - `createPhysicsUpdate()` with nothing\n// at all is movement that stops against things and says nothing about them, which is what a game wants for\n// terrain and walls. Movement that walks straight through everything is the bare physicsUpdate below.\n//\n// A callback cannot be sent to a worker (functions do not survive postMessage), which is why it is baked in\n// here rather than passed to PhysicsSystem: the worker file imports this module and gets the callback with it.\nexport function createPhysicsUpdate<\n\t// Constrained to a map that has the physics components in it rather than to any map at all, because the\n\t// blocks the update works on - `T` below - are the game's own components narrowed to the transform and\n\t// velocity it moves, so the map has to be able to see those two in there. Every game already spreads\n\t// physicsRegistry into its own, which is the same requirement PhysicsSystem makes.\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 and read back by each entity update that follows it, which is the whole reason it can be\n\t// a plain closure variable: a run is one unbroken pass - preRun, then every entity, then done - so there is\n\t// never a second run part way through this one to overwrite it.\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\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 two ends of the segment a renderer blends along are this\n\t\t\t// step's start and this step's end rather than positions from either side of it.\n\t\t\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\n\t\t\t// preRun builds the tree before any entity is updated, so there is only nothing here for an update\n\t\t\t// that was called by hand without one. Nothing to sweep against then, and the move is the plain\n\t\t\t// integration.\n\t\t\tif(!broadphase) {\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// Where the entity may actually get to is worked out here, off the blocks rather than in them: the\n\t\t\t// position is only written once the answer is known, so an entity is never inside another one - not\n\t\t\t// even for the moment it would take to push it back out.\n\t\t\t//\n\t\t\t// The sweep happens whether or not the game gave a callback. Coming to rest against what is in the\n\t\t\t// way is what physics *does* about a collision; `onCollision` is what the game does about it, and\n\t\t\t// plenty of games want the first without the second. Movement that ignores everything around it is\n\t\t\t// the bare physicsUpdate below rather than an option here.\n\t\t\tconst sweep = broadphase.sweep(self, moveX, moveY);\n\t\t\tmove(entityId, components.transform, moveX * sweep.fraction, moveY * sweep.fraction, callbacks, world.reportMoves);\n\t\t\t// Straight after the move and before any callback, so the stamp covers exactly the pair physics itself\n\t\t\t// produced. A callback that goes on to write the transform is a move the game made rather than one\n\t\t\t// this step did, and it is blended towards on the next frame the same way a teleport is.\n\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\tif(!onCollision) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Collisions are reported here, straight after the entity has been put down, so a callback acts on\n\t\t\t// where it has actually ended up rather than where it was - and a bounce that flips its velocity is\n\t\t\t// applied by the next run's move. Only the entity that moved gets the call; whatever it hit gets its\n\t\t\t// own when its turn comes, or never, if it is something the system does not move.\n\t\t\tbroadphase.forEachOverlapping(self, other => {\n\t\t\t\tonCollision(world, self, other, queries, callbacks);\n\t\t\t});\n\n\t\t\t// Then whatever the move was stopped *against*, which the overlap check above cannot find by design:\n\t\t\t// the entity was put down touching it rather than through it. The two lists never share an entry, so\n\t\t\t// nothing here has already had its callback.\n\t\t\tfor(const other of sweep.blocking) {\n\t\t\t\tonCollision(world, self, other, queries, callbacks);\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 whether or not anything is going to be told about what it\n\t\t\t\t// found, 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 is the one hook that sees every entity at once, which makes it the only place the tree can be\n\t// built from a single consistent moment - before any of this run's movement has happened.\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// Shared update logic used by both ComponentSystem backends: the main-thread ComponentWebWorker runs it\n// directly (PhysicsSystem passes it as `updateFunction`), while a game's worker file imports it and hands it\n// to createComponentWorker. Keeping it in one place means both paths run identical logic.\n//\n// This is movement only, and the *only* way to get movement with no collision detection at all: it walks an\n// entity straight through anything in its way, which is what a game wants for something that has no business\n// noticing the world - a drifting particle, a camera, a projectile that only its own system cares about. A\n// game that wants entities stopped by each other, with or without a callback about it, builds its update with\n// createPhysicsUpdate above instead, which sweeps around this same move.\nexport const physicsUpdate: EntityUpdateFunction<PhysicsComponents, PhysicsUpdateComponents, PhysicsWorld> = (world, entityId, components, queries, callbacks) => {\n\t// elapsedTime is in milliseconds (what BaseWorld#update is driven with), while velocity is in world units\n\t// per second - so convert before integrating.\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 has to happen *before* the move: where the entity is standing now becomes\n// the `prev` the next frame's render position is blended out of, along with how much simulated time the segment\n// between the two is going to cover.\n//\n// The duration is published rather than left to be assumed, because a run does not always cover one step: one\n// that comes back late leaves more than a step's worth of time banked and the next one takes two at once. A\n// renderer dividing that segment by the step instead of by what it really covers draws it at double speed.\n//\n// Nothing is written for an entity without the component, which is what makes interpolation opt-in per entity\n// rather than a cost every world pays.\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// And the half that has to happen after it. Written unconditionally - outside `move`'s \"did not go anywhere\"\n// early return - so an entity pressed against a wall publishes a step where `prev` equals where it still is,\n// rather than going quiet and leaving a renderer blending against whatever it was doing before it stopped.\n//\n// The tick goes last and through storeFloat32, which is an `Atomics.store` through an Int32Array view of the\n// same memory: a release store, so a reader that sees this tick is guaranteed to also see the `prev` and the\n// transform that belong with it. 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 a move that has already been decided on - by the integration above, or by what a sweep allowed of it -\n// and reports where the entity ended up. Because the blocks live in a SharedArrayBuffer, writing them here is\n// immediately visible through `entity.components.transform` on the main thread.\n//\n// This is the one place the position is written, and it is only reached once the final position is known: the\n// distance is worked out on plain numbers first so that nothing ever reads a transform mid-decision.\n//\n// The move is applied with addAtomicFloat32 rather than `+=`: the position is shared memory that another\n// thread (a game's own system, or a later physics system) may be adding to at the same moment, and a plain\n// read-modify-write would silently drop one of the two moves. Its `max` cap is not wanted here - physics\n// does not bound how far something may travel - so it is passed Infinity, leaving a plain atomic add.\n//\n// A move that did not go anywhere is not reported, which is what keeps a world of still entities quiet - and an\n// entity pressed up against something it cannot move past reports nothing at all rather than a change per run.\n// An entity that did move is reported once however many axes it moved along: a listener keyed off position - a\n// spatial index, a minimap, a sprite - wants the place rather than the axis, and it reads the whole transform\n// out of shared memory anyway.\n//\n// `reportMoves` is what makes even that skippable. It is not a saving on this line - it is a saving on the id\n// that would have been pushed into the run's event array, and on that whole array being cloned back across the\n// worker boundary once a step. A game reading positions through the interpolation component wants nothing to\n// do with it: the event fires at the physics rate, which is the rate it is drawing *around*. See\n// PhysicsSystem, which sets this from whether anything is actually listening.\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// The id on its own: the block above is what the main thread reads the position out of, and it is the same\n\t// memory, so there is nothing here worth sending it a copy of.\n\tcallbacks.emitSystemEvent(POSITION_UPDATED_EVENT, entityId);\n}\n\n// The one callback move reaches for, narrowed to the event physics reports. ComponentSystemCallbacks takes\n// any event name, which says nothing about what to listen for - so the moves are handed over through this\n// instead.\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 { BaseWorld, ComponentDefinitionMap, ComponentMap, 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// How long a physics step covers when a game does not say, in the milliseconds `BaseWorld#update` is driven\n// with. 20Hz rather than a frame: collision is by a distance the whole of it, and running it three times less\n// often is three times less of the frame spent on it, for a simulation that is no less correct - only coarser.\n// What it costs is smoothness, and that is what InterpolationSystem is for.\n//\n// `deltaBetweenRuns: 0` puts it back on every frame, which is what a game with very fast entities and no\n// interpolation wants: a move is swept where it *ends*, so a longer step is a longer move, and a move longer\n// than the thing in its way is thick goes 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\n\t// URL(...))` call to be written out in the game's own source, so the game supplies the file:\n\t//\n\t// // physics.worker.ts\n\t// import { createComponentWorker } from '@daneren2005/shared-memory-ecs';\n\t// import { physicsUpdate } from '@daneren2005/shared-memory-physics';\n\t// createComponentWorker(self, physicsUpdate);\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 ComponentWebWorker even when a getWorker is supplied. Defaults to true when no\n\t// getWorker was given, since there is then nothing to run the update on.\n\tforceMainThread?: boolean\n\t// The update to run, defaulting to plain movement that walks through anything in its way. A game that\n\t// wants its entities stopped by each other builds one with createPhysicsUpdate and passes the *same*\n\t// function its worker file hands to createComponentWorker, so both backends behave identically.\n\t// createPhysicsUpdate stamps what it needs onto the function, so `optional` and the collidable query below\n\t// come across with it and do not have to be repeated here.\n\tupdateFunction?: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> }\n\t// Extra components to send along with each entity's transform and velocity, for an update function built\n\t// by hand rather than through createPhysicsUpdate. Overrides what the update function declared.\n\toptional?: Array<keyof C & string>\n\t// Whether to gather the collidable query - every entity with a transform, whether it moves or not - and\n\t// send it along for the update's collision detection. Defaults to whatever the update function declared,\n\t// so it is on for any update built by createPhysicsUpdate (which always sweeps, callback or not) and off\n\t// for the bare physicsUpdate, which is movement that notices nothing.\n\tcollision?: boolean\n\t// Whether each run reports what moved through POSITION_UPDATED_EVENT. **Defaults to whether anything is\n\t// listening**, which is nearly always the right answer and is checked per run, so a game that adds or drops\n\t// a listener needs to do nothing here.\n\t//\n\t// It is worth a setting at all because the bill is paid in the worker: an id per moved entity pushed into\n\t// the run's event array, and that array structured-cloned back across the boundary. Nearly every entity\n\t// moves on nearly every run, so at a few thousand entities that is a few thousand ids a step for a listener\n\t// that may not exist. Force it `true` only for a listener attached somewhere this system cannot see.\n\treportMoves?: boolean\n}\n\n// Moves every entity that has both a transform and a velocity, and (when the update function detects\n// collisions) reports the entities that overlap to the game's callback. Generic over `C`, the game's full\n// component map, so a game's own world passes straight in; over `T`, the blocks its update function touches,\n// which is wider than transform + velocity once a game adds `optional` components of its own; and over `W`,\n// the per-run data object, for a game that subclasses this to add more through `addDataToWorld`.\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// Counted up once per run and stamped onto every interpolated entity, so a renderer can tell a position it\n\t// has already blended from one physics has just replaced. Nothing does arithmetic with it, so it only has\n\t// to change - and a Float32 holds integers exactly to 2^24, which at a 50ms step is over nine days of\n\t// continuous simulation before it stops incrementing in the block.\n\ttick = 0;\n\n\t// Undefined means \"whenever something is listening\", which is what addDataToWorld works out per run.\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// An entity reads its own collide category and mask before it searches, so the body block has to travel\n\t\t// with the entities this system moves as well as with the ones they can run into - but only when there is\n\t\t// collision detection to read it, since otherwise it is a block per moving entity for nothing.\n\t\t//\n\t\t// The interpolation block goes along unconditionally, because the update has to publish into it for any\n\t\t// entity that has one and there is no flag that says which entities those are. It costs nothing for the\n\t\t// ones that do not: the ECS only puts a component in the message when the entity actually holds it.\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(!optional.includes('interpolation')) {\n\t\t\textraOptional.push('interpolation');\n\t\t}\n\t\tconst movingOptional = extraOptional.length ? [...extraOptional, ...optional] : optional;\n\n\t\tsuper(world, {\n\t\t\tname: options.name ?? 'PhysicsSystem',\n\t\t\t// A fixed step by default rather than every frame - see DEFAULT_PHYSICS_STEP_MS.\n\t\t\tdeltaBetweenRuns: options.deltaBetweenRuns ?? DEFAULT_PHYSICS_STEP_MS,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// Movement needs both, so an entity with only one of them is not in the system at all - though it can\n\t\t\t// still be collided with through the query below.\n\t\t\trequired: ['transform', 'velocity'],\n\t\t\toptional: movingOptional,\n\t\t\tupdateFunction,\n\t\t\t// Collision is between anything with a transform and a body, not only the entities this system moves,\n\t\t\t// so that a ship can hit a station that has no velocity of its own. It is only gathered when something\n\t\t\t// is actually going to look at it, since it means shipping a block for every entity in the world.\n\t\t\tqueries: collision ? {\n\t\t\t\t[COLLIDABLE_QUERY]: {\n\t\t\t\t\trequired: ['transform', 'body'],\n\t\t\t\t\toptional: ['velocity', ...optional],\n\t\t\t\t},\n\t\t\t} : undefined,\n\n\t\t\tforceMainThread: options.forceMainThread ?? !options.getWorker,\n\t\t\t// Only ever reached when a getWorker was supplied: without one forceMainThread defaults to true, so\n\t\t\t// the base class never asks for a worker.\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, which is what the interpolation block is published under, and says\n\t// whether this run should bother reporting what moved.\n\t//\n\t// A game that subclasses this to attach data of its own **must call `super.addDataToWorld(world)`**: this is\n\t// an ordinary override, so replacing it without the super call leaves `tick` 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 rather than once at construction, so a game is free to attach and drop the listener\n\t\t// whenever it likes. It costs one property read on a thread that is about to sweep every entity in the\n\t\t// world, and it saves the worker building and cloning an id per moved entity when nobody wanted them.\n\t\tworld.reportMoves = this.reportMoves ?? this.listenerCount(POSITION_UPDATED_EVENT) > 0;\n\t}\n}\n","import { loadFloat32 } from '@daneren2005/shared-memory-objects';\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.\n//\n// render = prev + (current - prev) * alpha\n//\n// **Every position this writes is one the simulation actually produced** - somewhere on a segment between two\n// real positions - which is what it buys over guessing forward from the current position and a velocity. An\n// entity that stopped against a wall is drawn easing into the wall and stopping, because that is what happened;\n// one that turned a corner is drawn turning the corner, rather than continuing into the old heading for a frame\n// and then being snapped back. There is nothing to tune and no case where it draws something the entity did\n// not do.\n//\n// The cost is one step of latency, always: what is on screen is where the world was a step ago, not where it is\n// now. At a 50ms step that is 50ms behind on every entity, in exchange for never being wrong.\n//\n// ## How far along the segment to draw\n//\n// Two rules, and between them they are the whole of it:\n//\n// 1. **Advance by a frame's worth of the segment, every frame.** A segment covers `duration` milliseconds of\n// simulated time and the frame just drawn covered `elapsedTime` of it, so the render position moves\n// `elapsedTime / duration` of the way along. That is the same size every frame however the frames and the\n// steps happen to line up, which is what smooth means.\n// 2. **Start the next segment when it lands, and drop whatever was left of the old one.** It is only ever a\n// small remainder - the cap below means a segment is never overshot - and it cannot be carried: see the\n// note at the reset for why a deficit is the one thing this arithmetic cannot draw.\n//\n// ## Why none of this is read off the physics system\n//\n// The tempting clock is the physics system's own accumulator: `currentDelta / deltaBetweenRuns` sweeps 0 -> 1\n// once per step with nothing to keep in sync. It is also **wrong**, and wrong in exactly the way that makes a\n// 60fps page stutter whenever a physics run takes longer than a frame.\n//\n// That accumulator resets when a run is *posted*. This block changes when that run *lands*, and on a worker\n// thread those are different moments. A run posted with 14ms of leftover and taking 30ms to come back leaves\n// one whole frame where the accumulator says \"0.3 of the way into the new step\" while the block still holds the\n// step before it - so the entity is drawn 0.3 along a segment it was drawn 0.96 along last frame. Backwards,\n// then a lurch forward when the step finally lands, on every single step.\n//\n// So the pacing is driven by what has actually arrived, and the segment's length is published with it rather\n// than assumed to be the step: a run that comes back late leaves more than a step's worth of time banked, and\n// the next one covers two steps at once. A slow physics run then costs latency and nothing else, which is the\n// entire point - a step drawn late but at the right speed looks identical to one drawn on time.\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 very entity on\n\t// another thread right now, and a `prev` from after the move paired with a transform from before it draws\n\t// the entity walking backwards along a segment it has already finished.\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. The render position is left exactly as it was\n\t// for this one frame rather than being drawn from a mismatched pair: an entity that holds still for 16ms is\n\t// invisible, and one that jumps to a position neither end of the segment agrees with is not.\n\tif(before !== after) {\n\t\treturn;\n\t}\n\n\t// No segment to be part way along: an entity physics has never moved - a station, a wall - or one whose\n\t// first step has not landed yet. Where it is is where to draw it.\n\t//\n\t// Progress is left *finished* rather than at zero, because that is what it means: there is nothing left to\n\t// draw of the nothing that has happened. It is also what makes the first real step start from the right\n\t// place - the rule below takes one whole segment off whatever was banked, and taking it off zero would open\n\t// with a full segment of debt the entity could never work off.\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, so start again at the beginning of it - which is where the entity has just been drawn\n\t\t// anyway, because the new segment's `prev` is the old one's end.\n\t\t//\n\t\t// Whatever was left undrawn of the old segment is **dropped** rather than carried, and that is the whole\n\t\t// decision here. Carrying it is only possible as a *deficit* - the cap below means progress can never\n\t\t// arrive above 1 - and a deficit cannot be drawn: there is no third position to blend back into, so a\n\t\t// negative progress just pins the entity at `prev`, which is further forward than where it was. The debt\n\t\t// then never gets paid, grows by the same amount every step, and the entity lurches a whole segment each\n\t\t// time. Dropping it costs a fraction of a frame on the step that lands early, and costs nothing at all\n\t\t// when the step and the frame divide into each other - which at a 50ms step and 60fps they do.\n\t\tprogress = 0;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t}\n\n\t// Capped at the end of the segment, which is what absorbs a slow physics run. While waiting for the next\n\t// step the entity sits at the newest position the simulation actually produced, and the time spent waiting\n\t// is **dropped** rather than banked - so when the step does land there is no debt to spend all at once and\n\t// the next frame carries on at the same speed as every other. A worker that is consistently 30ms late costs\n\t// 30ms of latency and nothing else; only *variance* in how late it is can be seen at all.\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// As on PhysicsSystem, and defaulted the other way round: this runs on the main thread unless a game asks\n\t// otherwise, since a render position read one frame after it was computed is a render position one frame\n\t// stale, which is the latency this system exists to keep down.\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 not wired to the physics system at all**, and that is deliberate rather than convenient. Everything it\n// needs - the two positions, how much simulated time lies between them, and whether that pair is new - is in the\n// block, published by whichever run wrote it. Pacing off the physics system instead would mean pacing off when a\n// run was *posted*, and on a worker thread that is not when its results arrive; see interpolation-update.ts for\n// what that costs.\n//\n// So it takes no configuration, and it follows a step the game retunes mid-flight with nothing told to it:\n//\n// world.addSystem(new PhysicsSystem(world, { getWorker }));\n// world.addSystem(new InterpolationSystem(world));\n//\n// Ordering against the physics system is a preference rather than a requirement - going after it means a step\n// is picked up on the frame it happened rather than the one after, which is one frame of latency and nothing\n// else. Pause and `timeScale` need no code either: `BaseWorld#runUpdate` skips every system while paused and\n// scales the elapsed time it hands them, so the pacing stops and slows with the simulation 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 entire point of it.\n\t\t\tdeltaBetweenRuns: 0,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// A velocity is never read - the blend is between two positions the simulation already produced - so\n\t\t\t// something with no velocity of its own is in this system on exactly the same terms as a ship, and gets\n\t\t\t// a render position that tracks its transform.\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 in the world, how big it is, and which way it is facing. This is 2D physics, so the\n// transform is a position, the box that sits around it, and the angle that box is rotated to.\nexport interface TransformComponent {\n\tindex: number\n\tx: number\n\ty: number\n\twidth: number\n\theight: number\n\tangle: number\n}\n\n// How big an entity is and which way it starts out facing. This is the defining half of a transform: it is\n// what a game's entity template says, the same for every ship of a type, and it is what gives an entity a\n// transform at all - a config with neither a width nor a height does not load one.\n//\n// `width`/`height` are the *unrotated* size of the box, and are required: a box with no area never overlaps\n// anything, so an entity without a real size could never collide.\n//\n// `angle` is in radians, measured counter-clockwise from the +x axis, so it feeds Math.cos / Math.sin\n// directly. Nothing in this library writes it - it is the game's to set, usually from its heading - which is\n// why it is a starting facing here rather than saved state.\n//\n// A round entity can give a `radius` **instead of** a width and a height, which loads as a width and a height\n// of its diameter - so there is still only one size in the block however the config spelled it. Giving both\n// throws rather than picking a winner.\nexport interface TransformConfig {\n\twidth: number\n\theight: number\n\tangle?: number\n\tradius?: number\n}\n// Where the entity is, which is the half that is live runtime state: the physics system moves it every run,\n// so it is what has to round-trip through `save` for a saved world to resume where it left off. The size and\n// facing come back from the game's own template rather than the save.\n//\n// `x`/`y` are the *centre* of the box, not a corner: the box is rotated about that point and collision\n// projects out from it in both directions.\nexport interface TransformSerialization {\n\tx: number\n\ty: number\n}\n\n// Indexes into the backing Float32Array block. The physics update reads the same offsets off the raw shared\n// block, so they are exported for it (and for any game system that touches the block directly).\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 is what marks a config as having a transform at all. x/y are not in the list: they come back off\n\t// a save rather than out of a game's entity template, so on their own they do not describe an entity that\n\t// has a place in the world.\n\tloadProperties: ['width', 'height', 'radius'],\n\tload(entity, memory, config) {\n\t\t// A radius is the same size said differently, so it is turned into a width and a height here and nothing\n\t\t// downstream has to know which way the config was written. Both at once is a config that cannot be\n\t\t// honoured either way round, so it is refused rather than silently resolved.\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 is moving, in world units per second, along each axis. An entity with both a\n// transform and a velocity is moved by the physics system every run.\nexport interface VelocityComponent {\n\tindex: number\n\tvelocityX: number\n\tvelocityY: number\n}\n\n// Velocity is named per-axis rather than plain `x`/`y` because entity configs are flat and shared across\n// every component - `x`/`y` already belong to the transform. Either axis on its own is enough to give an\n// 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// Velocity is both the config an entity spawns with and live runtime state (a game's own systems apply\n// forces to it), so the whole thing round-trips back out through `save`.\nexport type VelocitySerialization = VelocityConfig;\n\n// Indexes into the backing Float32Array block. The physics update reads the same offsets off the raw shared\n// block, so they are exported for it (and for any game system that touches the block directly).\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 is willing to collide *with*. Only entities with a body take part\n// in collisions at all - but a body is loaded for anything with a size, so in practice the way to opt an entity\n// out is `collideCategory: 0` rather than leaving the component off.\n//\n// The categories themselves are the game's to name. This library only ever reads them as bits, so a game\n// declares whatever set it needs (ground, air, projectile, terrain, ...) and hands the numbers in through the\n// config.\nexport interface BodyComponent {\n\tindex: number\n\tshape: number\n\tcollideCategory: number\n\tcollideMask: number\n\t// Whether this body is a sensor: it takes part in collision *detection* - a mover that overlaps it still gets\n\t// its onCollision, and it can still be found by a spatial-index search - but it stops nothing. Nothing is\n\t// swept short against it and nothing bounces off it, so a solid entity passes straight through where it would\n\t// come to rest against an ordinary body. See canCollide for the category/mask rules, which a sensor obeys\n\t// unchanged: sensor is only about the *response*, not about which pairs are looked at.\n\tsensor: boolean\n}\n\n// The shapes a body can be. All three are held in the same transform - a position, a width, a height and an\n// angle - so which one an entity is changes what that data means rather than where it lives:\n//\n// SHAPE_RECTANGLE - a width x height box turned to `angle`\n// SHAPE_CIRCLE - a circle of diameter `width`, which `angle` does not affect\n// SHAPE_CAPSULE - a capsule `width` from end to end and `height` thick, lying along the way it faces\n//\n// See src/math/shapes.ts for what each one means geometrically.\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// What a body collides as when the game does not say: a single bit, so a world where nothing sets a category\n// behaves exactly as it did before categories existed.\nexport const DEFAULT_COLLIDE_CATEGORY = 1;\n// ...and what it is willing to collide with: every bit, so a default body meets anything whose own mask lets\n// it. 0xFFFFFFFF does not fit in a signed int, but it is stored in a Uint32Array and JS bitwise operators\n// work on the same 32 bits, so it ANDs correctly against any category a game can supply.\nexport const DEFAULT_COLLIDE_MASK = 0xFFFFFFFF;\n\n// Whether two bodies collide: each side's mask has to accept the other's category.\n//\n// The test is deliberately **symmetric**, so a pair either collides or does not regardless of which one moved\n// into which. That matters because collisions are reported per entity as each one moves: a rule that depended\n// on the direction would make the outcome depend on the order the system happened to move them in. A game\n// that wants a one-sided *response* - a projectile that hurts a unit without the unit hurting it back - gets\n// that by checking `other` inside its own onCollision, not by the two entities disagreeing about whether they\n// touched.\n//\n// So a ground unit that should be hit by projectiles has to name projectiles in its own mask, not just the\n// other way round. `collideCategory: 0` collides with nothing at all, since every mask ANDs to 0 against it.\n//\n// `&` yields a signed 32 bit result, so a mask with the top bit set comes back negative - compare against 0\n// rather than testing for a positive number.\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 is what loads a body, the same way it is what loads a transform: anything big enough to be in the\n// world is collidable by default, and the collide properties are there to narrow that rather than to grant it.\n// They are all *defining* config out of a game's entity template, which is why there is no `save` below - a\n// reloaded entity gets its category and mask back from the template, not from the save.\nexport interface BodyConfig {\n\t// Not read by the loader, only listed in `loadProperties`: they are what marks a config as describing\n\t// something that has a place in the world, and so something that can be run into.\n\twidth?: number\n\theight?: number\n\n\t// One of the SHAPE_ constants above. Defaults to a circle for a config given a `radius` and a rectangle\n\t// otherwise, so only capsules have to name their shape.\n\tshape?: number\n\t// Not read here either - the transform is what turns it into a width and a height - but a config that gives\n\t// a radius is describing a circle, and that is what decides the default shape above.\n\tradius?: number\n\t// The bit(s) this entity collides as, and the bits it is willing to collide with. See canCollide.\n\tcollideCategory?: number\n\tcollideMask?: number\n\t// Makes the body a sensor: found and reported like any other, but never blocking or bounced off, so movers\n\t// pass through it. Defaults to false - a body that says nothing is solid. See BodyComponent#sensor.\n\tsensor?: boolean\n}\n\n// Indexes into the backing Uint32Array block. The collision broadphase reads the same offsets off the raw\n// shared block, so they are exported for it (and for any game system that touches the block directly).\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 an ordinary solid body. Held in the block rather than as a JS flag so the broadphase\n// and the bounce - both of which read the raw shared array on the worker thread - can see it the same way they\n// see the shape and the collide bits.\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 touching the block directly reads the flag the\n// same way the library's own broadphase and bounce do, rather than hard-coding the offset and the `!== 0`.\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\t// Stored as 1/0 in the block but read and written as a boolean here, so a game toggles a sensor on and\n\t\t\t// off the same way it reads it - the block form is an implementation detail the broadphase shares.\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 is thrown on rather than defaulted: silently treating it as a rectangle would leave an\n// entity colliding with the wrong outline, which is far harder to spot than a config that fails to load.\n//\n// A config that says nothing is read off the size it gave instead - a `radius` describes a circle, and anything\n// else is a rectangle - so a game only has to name a shape for the one case its size cannot imply.\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 of its speed an entity keeps when it bounces off something it ran into, as a fraction of the part of\n// its velocity that pointed *into* what it hit. The physics system reflects that entity about the contact\n// normal on collision and scales the reflected part by this - see the bounce in createPhysicsUpdate.\n//\n// 1 - a perfect bounce: the velocity into the surface comes straight back out at the same speed, so a\n// head-on hit simply flips the velocity and the entity keeps running forever.\n// 0.5 - half of that speed comes back, so the entity loses half its speed into the wall on each bounce.\n// 0 - nothing bounces: the velocity into the surface is cancelled and only the part sliding along it is\n// left, so the entity comes to rest against the wall rather than rebounding off it.\n//\n// Only entities that actually bounce carry this - it is loaded off a config that names `bounciness`, so a world\n// of terrain and walls pays nothing for it. An entity without it is still stopped by what it runs into (the\n// sweep does that for everything); it just does not turn around.\nexport interface BouncinessComponent {\n\tindex: number\n\tbounciness: number\n}\n\n// A config that gives a `bounciness` is what loads the component, the same way a size is what loads a transform.\n// It is defining config out of a game's entity template - the material an entity is made of rather than live\n// state - which is why there is no `save`: a reloaded entity gets its bounciness back from the template.\nexport interface BouncinessConfig {\n\tbounciness?: number\n}\n\n// The one index into the backing Float32Array block. The bounce reads the same offset off the raw shared block,\n// so it is exported for it (and for any game system that touches the block directly).\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 stops being the same thing as where it is the moment physics runs\n// less often than the screen refreshes. A 50ms step against a 16ms frame means the transform only changes on\n// one frame in three, so an entity drawn straight off it visibly stutters; this block holds a position that\n// changes every frame instead.\n//\n// It is filled by InterpolationSystem by blending between the position the entity had before the last physics\n// step and the one it has after it, so **every position in here is one the simulation actually produced** -\n// somewhere on the segment between two real positions, never a guess past the end of one. The cost of that is\n// one step of latency: what is drawn is where the entity was a step ago, not where it is now.\n//\n// Nothing in the simulation ever reads this back. Collision, the sweep and the spatial index all work off the\n// transform, so a render position can never feed back into where an entity is allowed to go - which is also\n// what keeps it safe under lockstep multiplayer, where two machines running at different frame rates would\n// otherwise read different numbers out of the same tick.\nexport interface InterpolationComponent {\n\tindex: number\n\t// The render position. Rewritten every frame by InterpolationSystem - a game reads it and does not write\n\t// it, except through `snapEntity` after teleporting something.\n\tx: number\n\ty: number\n\t// Where the entity stood before the physics step now being blended. Written by the physics update.\n\tprevX: number\n\tprevY: number\n}\n\n// `interpolate` is what gives an entity this component at all, the way a size is what gives it a transform. It\n// is opt-in per entity rather than automatic on anything with a transform, because it is 20 bytes and a\n// per-frame visit for something a game may never draw.\n//\n// `x`/`y` are read off the same flat config the transform loads from, so a new entity's render position starts\n// where it spawned rather than sliding in from the origin on its first frame.\nexport interface InterpolationConfig {\n\tinterpolate?: boolean\n\tx?: number\n\ty?: number\n}\n\n// Indexes into the backing Float32Array block. The physics update and the interpolation update both read\n// these offsets off the raw shared block, so they are exported for them (and for any game system that wants\n// the render position without going through the accessors).\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, as a fraction of it: 0 at `prev`, 1 at the transform.\n// Written only by the interpolation system.\n//\n// Kept as a fraction rather than as milliseconds so that the leftover from one segment carries onto the next\n// one without needing to remember how long the old segment was - which matters because they are not all the\n// same length (see INTERPOLATION_DURATION_INDEX). It is allowed to go slightly **negative**, which is what\n// stops the drawn speed rippling: the frames that pass between two steps add up to a frame more or less than\n// the step covers, and carrying the difference forward instead of clamping it away is what keeps every frame's\n// worth of drawn movement identical.\nexport const INTERPOLATION_PROGRESS_INDEX = 4;\n// The step the interpolation system last reconciled against, so it can tell a step it has already started\n// drawing from one physics has just published. Written only by the interpolation system.\nexport const INTERPOLATION_SYNCED_TICK_INDEX = 5;\n// How much simulated time this segment covers, in milliseconds - the `elapsedTime` of the run that produced it.\n// Written by the physics update.\n//\n// Nearly always the physics step, and *not* assumed to be: a run that comes back late leaves more than a step's\n// worth of time banked, and the next one covers two steps in one go. Dividing that segment by the step rather\n// than by what it really covers would draw it at double speed, which is a stutter in exactly the case - physics\n// falling behind - where this component is supposed to be hiding one.\nexport const INTERPOLATION_DURATION_INDEX = 6;\n// The physics step this block's `prev` belongs to, written **last** by the physics update with a release store.\n// It is a publication stamp rather than anything a reader does arithmetic with: physics writes `prev` and then\n// the transform, on another thread, while the main thread is reading both - so a reader that sees the same tick\n// on either side of its own reads knows the two it got belong together. Reading them from either side of a\n// step would blend towards a position the entity has already left, which is the one mismatch that draws an\n// entity moving backwards.\n//\n// It is also the *arrival* signal, which is the whole reason the pacing lives in this block rather than being\n// read off the physics system's accumulator. That accumulator resets when a run is **posted**; these three\n// fields change when it **lands**, and on a worker those are not the same moment.\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// Seeded with the spawn position on both sides of the blend, and with the pacing at rest - so an entity\n\t\t// added between physics steps is drawn standing still where it was put rather than sliding in from\n\t\t// wherever the block happened to be last used. PhysicsSystem's first run stamps tick 1, so the 0 here is\n\t\t// what makes that first step read as a new one to reconcile against.\n\t\t//\n\t\t// A duration of 0 reads as \"no segment yet\", which draws the entity at its transform - which is also what\n\t\t// something the physics system never moves is left on for its whole life.\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 in here is derived from the transform and rebuilt on the next physics step, and\n\t// `interpolate` itself is defining config that comes back off the game's own entity template.\n};\n\n// Anything with the two components this reads, which is any entity from a world that registered\n// `physicsRegistry` - written structurally so it does not need the game's component map to say so.\nexport interface SnappableEntity {\n\tcomponents: {\n\t\ttransform?: TransformComponent\n\t\tinterpolation?: InterpolationComponent\n\t}\n}\n\n// Throws away the segment an entity is part way along and puts it, drawn and simulated, at the same place.\n//\n// **Call this after writing a transform directly.** Interpolation blends from where an entity was towards\n// where it is, so a game that teleports something by assigning `transform.x` has not moved it - it has made the\n// segment 500 units long, and the entity is drawn sliding the whole way across the map over the next step. This\n// is the one thing blending between real positions cannot work out for itself, because a long move and a\n// teleport are the same two numbers.\n//\n// It is not needed for anything physics did: a velocity change, a collision, an entity coming to rest against a\n// wall are all steps the simulation took and are all 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 so its world\n// allocates their memory pools alongside its game specific components:\n//\n// const registry = { ...physicsRegistry, health: healthDefinition };\n// const world = new BaseWorld(registry);\n//\n// The names here are what the physics systems query on, so they must be kept 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 that the physics systems need. Declared as a type alias (not an\n// interface) so it satisfies the ECS's `ComponentMap` index signature. Systems here are generic over the\n// game's full component map and only require that it extends this.\nexport type PhysicsComponents = {\n\ttransform: TransformComponent\n\tvelocity: VelocityComponent\n\tbody: BodyComponent\n\tbounciness: BouncinessComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The slice InterpolationSystem needs, which is a strictly smaller one: it never looks at a velocity or a body.\n// Split out so a game can be generic over it on its own, and so the interpolation update declares exactly what\n// it touches rather than inheriting the physics list.\nexport type InterpolationComponents = {\n\ttransform: TransformComponent\n\tinterpolation: InterpolationComponent\n};\n\n// The same components as their concrete backing arrays, which is the form a system's update function sees them\n// in on the worker thread. `body` is optional here because it is optional on the query that moves entities -\n// movement does not need it, and it is only sent along when something is going to collide. The collidable\n// query requires it, so CollisionComponents narrows it back to guaranteed for the entities found through that.\n//\n// A game that hands extra components to its collision callback widens this with its own:\n//\n// type GameUpdateComponents = PhysicsUpdateComponents & { health?: Float32Array };\nexport type PhysicsUpdateComponents = {\n\ttransform: Float32Array\n\tvelocity: Float32Array\n\tbody?: Uint32Array\n\t// Optional in the same way and for the same reason: an entity that does not bounce does not have the\n\t// component, and it is only ever read for the moving entity itself, which the sweep bounces on collision.\n\tbounciness?: Float32Array\n\t// Optional in the same way and for the same reason: an entity a game never draws interpolated does not have\n\t// the component, and the update writes nothing for it.\n\tinterpolation?: Float32Array\n};\n\n// The blocks the interpolation update works on. Both are required - the system's query asks for both - which\n// is what lets the update read them without a guard per entity per frame.\nexport type InterpolationUpdateComponents = {\n\ttransform: Float32Array\n\tinterpolation: Float32Array\n};\n","// Geometry for the shapes a transform plus a body describe, all of them centred on (x, y) and turned `angle`\n// radians counter-clockwise. Everything here is pure number math on loose arguments rather than objects, so\n// the collision sweep can call it per pair without allocating.\n//\n// The three shapes are one idea in three settings: **an oriented core, grown by a radius**.\n//\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//\n// A circle is just a capsule whose segment has no length, so there are only two kinds of core - a box and a\n// segment - and therefore only three pair tests rather than one per combination of shapes. See shapesOverlap.\n\nimport { SHAPE_CAPSULE, SHAPE_CIRCLE, SHAPE_RECTANGLE } from '../components/body-component';\n\n// How far a capsule's core segment reaches from the centre, along the direction it faces. The caps are part\n// of the length, so a 40 x 10 capsule is 40 from end to end: 15 of segment either way plus a 5 radius cap.\n//\n// Clamped at 0, so a capsule told to be shorter than it is wide collapses to a circle of its own width rather\n// than turning itself inside out.\nexport function capsuleHalfLength(width: number, height: number): number {\n\treturn Math.max(0, (width - height) / 2);\n}\n\n// The radius of a shape's cap: half the diameter for a circle, half the thickness for a capsule, and nothing\n// at all for a rectangle, whose outline is its core.\n//\n// A circle takes its diameter from `width` and ignores `height` - loading one from a `radius` config sets both\n// to the same thing, so the two only ever disagree if a game writes the block itself.\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// Whether a shape encloses no area at all, and so can never overlap anything - not even something sitting\n// exactly on top of it. This is what keeps an entity the game never gave a size out of collisions, and which\n// of the two sizes matters depends on the shape: a circle is nothing without its width, while a capsule with\n// no thickness is a bare line however long it is.\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 width of the *axis aligned* box that fully contains a shape, whatever it is turned to - the box the\n// broadphase indexes it under, so it has to hold the whole shape or the tree would rule out its own candidates.\n//\n// A capsule always fits inside the rectangle its width and height describe, being that rectangle with its ends\n// rounded off, and so does a circle loaded from a `radius` (whose width and height agree). A circle is its\n// *width* alone though, so one whose height was written smaller by hand is taller than that rectangle - which is\n// why this is asked per shape rather than measured off the rectangle and trusted for everything.\nexport function shapeHalfWidth(shape: number, width: number, height: number, angle: number): number {\n\tif(shape === SHAPE_CIRCLE) {\n\t\t// A circle covers the same ground whichever way it is turned.\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}\n// The same for the y axis: the capsule's segment leans the other way, and the circle is still a circle.\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.\n//\n// Because a circle is a capsule with no length, every pair falls into one of three cases, picked by a switch\n// over the two shape constants with no indirection in the way:\n//\n// rectangle + rectangle - two boxes with no radius between them, which is the separating axis test below\n// round + round - the gap between the two core segments has to be under the two radii added together\n// rectangle + round - the gap between the core segment and the box has to be under the one radius\n//\n// Touching exactly counts as *not* overlapping in all three, matching orientedBoxesOverlap: the comparisons\n// are strict, so a capsule whose cap grazes a wall has not hit it.\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// Overlapping is the same relation whichever way round it is asked, so the one mixed case is written once\n\t\t// 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\t// b is round either way from here, so take its core segment now.\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 along the direction it faces: a capsule's segment, and nothing\n// for a circle (a point) or a rectangle (whose core is the whole box, handled separately).\nfunction coreHalfLength(shape: number, width: number, height: number): number {\n\treturn shape === SHAPE_CAPSULE ? capsuleHalfLength(width, height) : 0;\n}\n\n// Half the width of the *axis aligned* box that fully contains a rotated one. Turning a rectangle widens the\n// footprint it needs: each of its two sides contributes however much of itself now points along the x axis.\n// At angle 0 this is just width / 2, and at a quarter turn it is height / 2.\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}\n// The same for the y axis, with the two sides' contributions swapped.\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: two convex shapes miss each other if and\n// only if some axis exists that their projections do not both cover. For a pair of boxes only four axes can\n// ever be that axis - the two each box's own sides face along - so testing those four settles it.\n//\n// Touching exactly counts as *not* overlapping: the projections meet but do not cross. A box with no area\n// never overlaps anything either, even sitting inside another one - it collapses to a point or a line, and\n// the theorem has nothing to separate. Together those are what keeps an entity the game never gave a size\n// out of collisions entirely.\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\t// Everything is measured relative to a's centre, so only the offset between the two centres matters.\n\tconst offsetX = bX - aX;\n\tconst offsetY = bY - aY;\n\n\t// a's own two axes, then b's. Each box's sides face along (cos, sin) and (-sin, cos) of its angle.\n\treturn !separatedAlong(aCos, aSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(-aSin, aCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(bCos, bSin, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin)\n\t\t&& !separatedAlong(-bSin, bCos, offsetX, offsetY, aHalfWidth, aHalfHeight, aCos, aSin, bHalfWidth, bHalfHeight, bCos, bSin);\n}\n\n// Whether the two boxes' shadows on the axis (axisX, axisY) fail to reach each other. A box's shadow reaches\n// out from its centre by however far its own two half-sides project onto the axis, so the pair is separated\n// when the gap between the centres along that axis is at least the two reaches added together.\n//\n// The axes passed in are always a box's own sides, which are unit vectors, so the dot products below are\n// already true distances and nothing needs normalising.\nfunction separatedAlong(\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): boolean {\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 Math.abs(offsetX * axisX + offsetY * axisY) >= aReach + bReach;\n}\n\n// The squared distance between two line segments, each given as its two end points. Squared because that is\n// all the callers need - comparing it against a squared radius avoids a square root per pair.\n//\n// This is what settles every pair of round shapes: two capsules are their two segments grown by a radius each,\n// so they meet exactly when the segments come within the two radii of one another. A circle is a segment whose\n// ends are the same point, which this handles as the degenerate case rather than as a shape of its own.\n//\n// Walks the usual closest-point-between-segments argument: solve for the closest points on the two infinite\n// lines, then clamp back into the segments, re-solving the other parameter each time a clamp moves one. The\n// parallel case has no single solution, so it pins one end and lets the clamping find the rest.\nexport function segmentSegmentDistanceSquared(\n\taX1: number, aY1: number, aX2: number, aY2: number,\n\tbX1: number, bY1: number, bX2: number, bY2: number,\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\t// Two points - which is two circles, the most common pair of all.\n\t\treturn offsetX * offsetX + offsetY * offsetY;\n\t} else if(aLength <= 0) {\n\t\t// A circle against a capsule: only where it lands along the capsule's segment matters.\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 exactly when the two segments are parallel, which is where pinning aParam at 0 comes in: every\n\t\t\t// point along one is the same distance from the other, so any of them is as good an answer.\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 the closest point on b, so where it sits along a 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\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// The squared distance between a line segment and a rotated box. This is what settles a rectangle against a\n// circle or a capsule: the round shape is its segment grown by a radius, so it reaches the box exactly when the\n// segment comes within that radius of it.\n//\n// Measured in the box's own frame, where it is axis aligned and the whole thing reduces to a segment against a\n// rectangle sitting at the origin. Two disjoint convex shapes always have their closest pair at a corner of\n// one against the other, so once the crossing case is out of the way the answer is the nearest of: each end of\n// the segment to the box, and each corner of the box to the segment.\nexport function segmentBoxDistanceSquared(\n\tx1: number, y1: number, x2: number, y2: number,\n\tboxX: number, boxY: number, boxWidth: number, boxHeight: number, boxAngle: number,\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\t// Both ends into the box's frame, so the box becomes the rectangle from (-halfWidth, -halfHeight) to\n\t// (halfWidth, halfHeight).\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// The crossing case has to be caught on its own: a segment straight through the middle of the box has both\n\t// its ends outside it and every corner some way off, so the nearest-pair search below would report a real\n\t// distance for two shapes that are already through each other.\n\tif(!segmentMissesBox(localX1, localY1, localX2, localY2, halfWidth, halfHeight)) {\n\t\treturn 0;\n\t}\n\n\treturn Math.min(\n\t\t// Each end of the segment against the box...\n\t\tpointBoxDistanceSquared(localX1, localY1, halfWidth, halfHeight),\n\t\tpointBoxDistanceSquared(localX2, localY2, halfWidth, halfHeight),\n\t\t// ...and each corner of the box against the segment, which is where a segment running past a box at an\n\t\t// angle comes closest to it.\n\t\tpointSegmentDistanceSquared(-halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(halfWidth, -halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t\tpointSegmentDistanceSquared(-halfWidth, halfHeight, localX1, localY1, localX2, localY2),\n\t);\n}\n\n// The squared distance from a point to a line segment: how far along the segment the point falls, clamped to\n// its ends, and then the gap from there.\nexport function pointSegmentDistanceSquared(x: number, y: number, x1: number, y1: number, x2: number, y2: number): number {\n\tconst deltaX = x2 - x1;\n\tconst deltaY = y2 - y1;\n\tconst length = deltaX * deltaX + deltaY * deltaY;\n\n\t// A segment with no length is just its own start point.\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\n\treturn gapX * gapX + gapY * gapY;\n}\n\n// Whether an axis aligned box at the origin and a segment fail to reach each other, by the same separating axis\n// argument as orientedBoxesOverlap: the box's own two axes, plus the one the segment faces along. Touching\n// exactly counts as reaching, so a grazing segment is measured as 0 away rather than as a near miss.\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\t// The segment's own axis. Both of its ends project to the same place on the normal, so one is enough - and\n\t// for a segment with no length the normal is (0, 0), which never separates and correctly leaves the two box\n\t// axes above to decide whether the point is inside.\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// The squared distance from a point to an axis aligned box at the origin: clamp the point onto the box and\n// measure what is left over. 0 for a point inside it.\nfunction pointBoxDistanceSquared(x: number, y: number, halfWidth: number, halfHeight: number): 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\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 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 the moment of contact a sweep has to get before it settles for it, in world units. Small enough\n// that the gap it leaves an entity resting against another is far below anything a game would draw, and large\n// enough that the halving below reaches it in a handful of steps.\nconst CONTACT_TOLERANCE = 1e-4;\n// A cap on that halving, so a move over a huge distance cannot turn into an unbounded number of overlap tests.\n// Each one narrows the answer by half, so this reaches the tolerance above for any move up to about sixteen\n// hundred units - and only an entity that is actually stopping somewhere ever spends a single one of them.\nconst MAX_REFINEMENTS = 24;\n\n// The name PhysicsSystem registers its extra query under, and the key the broadphase reads it back out of.\n// It holds everything with a transform and a body rather than only the entities the system moves, so a moving\n// entity can still run into a station, a wall or anything else that has no velocity of its own.\nexport const COLLIDABLE_QUERY = 'collidable';\n\n// The blocks a collision callback is handed for the entity that was run into. `transform` and `body` are the\n// guarantees - they are what the collidable query requires - while `velocity` and everything the game listed\n// in `optional` are there only if that entity has them.\nexport type CollisionComponents<T extends PhysicsUpdateComponents> = Partial<T> & {\n\ttransform: Float32Array\n\tbody: Uint32Array\n};\n\n// The entity that did the running into: the one being updated, so everything the system asked for is there.\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 at all.\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. It is called on the same thread the physics update\n// runs on, so it must be a plain function over the raw blocks - no entities, no world - and it reaches the\n// main thread only through `callbacks` (which report a changed property, a death, or an entity to spawn).\n//\n// It is called *per entity*, right after that entity has moved: `self` is the one that just moved and `other`\n// is what it has ended up on top of. Two moving entities that run into each other therefore get one call\n// each, with the roles swapped, which is what lets a callback act on itself - bouncing, taking damage - and\n// leave the other side to its own call. An entity with no velocity is never `self`, since the system never\n// moves it, but it is still found as `other`.\n//\n// `queries` is passed straight through so a callback that needs more than the two entities in front of it can\n// still walk the full collidable list, e.g. to credit a third entity for the kill.\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 is actually allowed to make, and what stopped it there.\n//\n// `fraction` is how far along the requested move it got: 1 for a clear path, 0 for an entity already pressed up\n// against something, and whatever is in between for one that came to rest part way.\n//\n// `blocking` is what it came to rest *against*, which the overlap test at that resting place will not report -\n// the whole point of stopping there is that the two are touching rather than through each other. More than one\n// entry means it wedged between them at the same moment, not that it should have stopped at the first.\nexport interface SweepResult<T extends PhysicsUpdateComponents> {\n\tfraction: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// How a move actually came out, given as the displacement to apply rather than a fraction of the one that was\n// asked for - because a move that had to drop an axis to get anywhere is no longer a scaling of the original.\n//\n// `blocking` is what it came to rest against, and is empty for a move that got where it was going - including\n// one that slid off a corner and completed along a single axis, which is touching nothing by the time it stops.\nexport interface MoveResult<T extends PhysicsUpdateComponents> {\n\tmoveX: number\n\tmoveY: number\n\tblocking: Array<CollisionEntity<T>>\n}\n\n// What `blocking` is for the sweeps that find nothing in the way, which is most of them: one shared empty array\n// rather than one per entity per run. Read-only to a caller by nature - there is nothing in it to iterate.\nconst NOTHING_BLOCKING: Array<never> = [];\n\n// The result of a per-axis sweep that hit nothing: the whole of that axis, with nothing in the way of it. Held\n// once rather than rebuilt for every axis a slide finds clear.\nconst CLEAR = { fraction: 1, blocking: NOTHING_BLOCKING };\n\n// Everything about the entity doing the searching, read out of its blocks once so that a sweep testing the same\n// pair a dozen times over does not read them a dozen times over. 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// Whether this entity is a sensor, so its move is never swept short: a sensor passes through whatever it moves\n\t// into rather than coming to rest against it. Constant for the whole of a searcher's sweep, so it is read out\n\t// here once rather than off the block per candidate.\n\tsensor: boolean\n}\n\n// One category's worth of the broadphase: every collidable entity that collides *as* that category, and an\n// R-tree over just those. A searching entity only looks in the buckets its own mask accepts, so a projectile\n// that can only hit units never walks the tree the terrain is in.\n//\n// Bucketed on the whole category *value* rather than a bit at a time, so an entity that collides as two things\n// at once still lives in exactly one bucket and can never be 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, entity by entity as\n// each one moves, what it might have hit.\n//\n// Everything is indexed where it stood at the *start* of the run, because that is the only moment all of the\n// entities agree on - by the time the third entity moves, the first two have already gone somewhere else. To\n// stay a true superset of what can actually collide, each box is grown by however far that entity could\n// travel before the run is out. The growth goes both ways rather than along the heading: a collision\n// callback is free to turn an entity around before it has had its own move, and a box grown only forwards\n// would then be pointing the wrong way.\n//\n// The tree is split by collide category rather than being one index over everything, so most of what an entity\n// cannot hit is ruled out a whole subtree at a time instead of one candidate at a time. Nothing is left empty:\n// a category with no collidable entities has no bucket at all, which also means Flatbush is never asked to\n// build an index over zero items.\nexport default class CollisionBroadphase<T extends PhysicsUpdateComponents> {\n\tprivate buckets: Array<CategoryBucket<T>> = [];\n\n\tconstructor(entities: Array<{ entityId: number, components: EntityUpdateComponents }>, seconds: number) {\n\t\t// Boxes are collected per category first because Flatbush has to be told how many items it will hold up\n\t\t// front, and how many that is only becomes clear once 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 query blocks generically as ComponentTypedArray since it cannot know what any given\n\t\t\t// game registered; narrow them to the concrete arrays the definitions allocate here, once, so neither\n\t\t\t// the search below nor the game's callback needs a cast of its own.\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 ever name category 0, so an entity that collides as nothing can never be the other half\n\t\t\t// of a collision either - dropping it here is exact rather than an approximation, and it is what makes\n\t\t\t// `collideCategory: 0` a way to opt an entity out of collisions 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, so dropping it here is exact rather than an\n\t\t\t// approximation - and it keeps an entity the game never gave a size out of the tree entirely.\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\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// Runs `handle` for everything `self` is really overlapping, having just moved.\n\t//\n\t// The tree narrows the field to whatever is anywhere near, then each of those is put through the full\n\t// rotated box test against where the two entities are *now* - which for an entity that has not had its own\n\t// move yet is still where it started. That is the price of collisions happening as each entity moves\n\t// rather than all at the end, and it evens out: the pair is looked at again from the other side once that\n\t// entity moves too.\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 with where this entity has actually ended up, ungrown: every box in the tree already allows\n\t\t// for its own entity's move, so allowing for this one's a second time would only widen the net.\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 (moveX, moveY) `self` may take before it runs into something, so that an entity comes\n\t// to rest against what is in its way instead of ending up inside it. The whole move is checked in one pass\n\t// for what it lands on, and only if something is there is the resting place refined - so there is one answer\n\t// for the move and it is the earliest contact of the lot, not whichever candidate happened to come 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\t// Nothing to work out for an entity that is not going anywhere, or that does not collide at all.\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 the way physics applies it, as the displacement to write rather than a fraction of what was\n\t// asked - because a move that had to drop or shorten an axis is no longer a scaling of the original. With\n\t// `slide` off it is `sweep` in different clothes: the whole move, or the swept-short part of it.\n\t//\n\t// With `slide` on, a diagonal move blocked where it wanted to go is resolved one axis at a time, so an entity\n\t// clipping a corner keeps running along the wall instead of sticking to it. Each axis is taken as far as it\n\t// can go on its own: the blocked one stops hard against the edge it hit, and the clear one slides its whole\n\t// length past - so the entity ends up right against what stopped it rather than hovering short of it. Where\n\t// the diagonal was only caught at the corner and each axis alone is clear, there is nothing to press against,\n\t// so it slides the full length of the axis the move was mostly along and leaves the other be.\n\t//\n\t// A clear move still costs the one endpoint pass and nothing else; only a blocked one does the axis work.\n\t//\n\t// Sliding is the caller's to ask for because it is the wrong answer for a bouncing entity: that one turns\n\t// around off whatever it hits rather than skating along it, so the bounce path resolves with `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// The common case: the whole move lands clear, settled by the one endpoint pass and nothing else.\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 on the diagonal, and asked to slide: resolve each axis on its own. The diagonal candidates\n\t\t// above only told us it is blocked *somewhere*; which axis that was is what the two passes here work out.\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 on its own reaches clear, so only the diagonal itself was caught and there is\n\t\t\t// nothing to press up against. Slide the full length of the axis the move was mostly along - keeping\n\t\t\t// most of its heading - and leave the other alone, since 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 runs into something. Take each as far as it goes: a clear axis slides its whole\n\t\t\t// length, a blocked one is refined right up to the edge it hit rather than dropped to nothing - which\n\t\t\t// is what puts the entity hard against the obstacle instead of floating a step short of it.\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 with no other axis to fall onto: come to rest where the move stops, the\n\t\t// same short move `sweep` gives, handed back as the displacement it works out to.\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 top of, asked where it *ends* rather than\n\t// anywhere along the way. That is what keeps a clear move - which is nearly every move - down to a single\n\t// shape test per candidate: an entity only ever walks its own path once something is genuinely in the way.\n\t//\n\t// The trade is that a move long enough to carry an entity clean past something is not stopped by it: by the\n\t// time the move is out there is nothing left to land on. That takes a single run covering more ground than\n\t// the thing in the way is thick, 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 is swept short by nothing: it passes through whatever it moves into rather than coming to rest\n\t\t// against it, so there is nothing to gather. It is still reported by forEachOverlapping - detection is\n\t\t// exactly what a sensor is for - but the *move* is never blocked, so this returns before any shape test.\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\t// Not where the move ends up, so nothing this move has to stop for.\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 straight through it and comes to rest only against the\n\t\t\t// next real body, so it is dropped as a candidate here and left to the overlap callback that follows.\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 - something the game put there, or another system pushed it\n\t\t\t// into. It cannot be what *this* move ran into, and blocking on it would pin the entity inside it for\n\t\t\t// good with no way back out, so the move is let through and the pair is left to the overlap callback\n\t\t\t// that follows it.\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 of where it asked to go, and this finds where. `clear`\n\t// is a fraction of the move it is known to fit at and `blocked` one it is known not to - the whole move to\n\t// begin with, since landing there is what put these candidates in the list - and halving closes the gap.\n\t//\n\t// Contact is halved in on rather than solved: three shapes at any rotation to one another have no single\n\t// formula for when a pair first touches, while the overlap test the narrowphase already has answers it for any\n\t// pair anywhere. The result is always taken from a position that tested clear, so wherever the entity is put\n\t// down it is genuinely not inside any of them.\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 position that was not clear: the entity has come to rest a hair\n\t\t// short of exactly there, so this is what it is up against.\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 up against something is left exactly where it is rather than crawling the last\n\t\t// fraction of a tolerance forwards every run, which would report 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` is allowed to collide with at all -\n\t// the tree search and the category rules, with no geometry beyond the boxes themselves.\n\t//\n\t// The collide masks are applied before any shape test rather than after: a pair that can never collide is\n\t// settled by two ANDs instead of a full separating-axis test.\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 for everything in the bucket rather than once per candidate:\n\t\t\t// they all collide as the same category, so either this entity's mask accepts the lot of them or it\n\t\t\t// accepts none of them.\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\t// Every collidable entity is in some bucket, including this one.\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, which does have to be per candidate: a bucket shares one category\n\t\t\t\t// but every entity in it 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\thandle(other);\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Combines what stopped each axis of a slide into one list, without repeating an entity that stopped both - a\n// single box in the corner of a diagonal move can be the thing each axis ran into. Either side being empty is\n// the common case (usually only one axis is blocked), so that side 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 out what a searching entity needs to be measured by, or undefined for one that cannot collide at all.\nfunction toSearcher<T extends PhysicsUpdateComponents>(self: MovingEntity<T>): Searcher | undefined {\n\t// An entity with no body does not collide at all. It can still be moved by the system - the query that\n\t// moves entities only asks for the body, it does not require it.\n\tconst body = self.components.body;\n\tif(!body) {\n\t\treturn undefined;\n\t}\n\n\t// Willing to run into nothing at all, so there is no bucket worth searching. Note this only stops it being\n\t// the entity that *finds* a collision: something whose mask accepts it can still find this one, and that is\n\t// the 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 searching entity, put down at (x, y) rather than wherever its own block says it is, overlaps\n// `other`. Taking the position as arguments is what 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 - that is what says where something\n// is and how big it is - while the body refines the box it is filed under to the shape it actually is. Anything\n// else a game queried travels along untouched, so a filter can read it back out.\nexport type SpatialComponents = {\n\ttransform: Float32Array\n\tbody?: Uint32Array\n};\n\n// One entity as the index hands it back: the id it was given plus whatever blocks came with it.\nexport interface SpatialEntity<T extends SpatialComponents = SpatialComponents> {\n\tentityId: number\n\tcomponents: T\n}\n\n// Which of the entities a query found are wanted. Run per candidate the tree turns up, so it is the place to\n// put whatever the index itself cannot know - the searcher's own id, which team something is on, whether it is\n// already dead - and everything it rejects is as if it were never in the box at all.\nexport type SpatialFilter<T extends SpatialComponents = SpatialComponents> = (entity: SpatialEntity<T>) => boolean;\n\n// An R-tree over a set of entities, for the questions a game asks about *where things are* rather than about\n// what is running into what: who is in this area, and what is the closest thing to this point.\n//\n// This is the same machinery the collision broadphase is built on, minus everything collision specific - there\n// are no collide categories, no room left for a move, and no shape tests. Every answer is in terms of the\n// axis aligned box each entity occupies, so an entity is \"found\" as soon as that box is reached, and picking\n// out the ones that really matter is the caller's to do with a `filter` (see SpatialFilter).\n//\n// Nothing here reads the blocks after the constructor, so the index is a snapshot: it describes where everything\n// was at the moment it was built. Build one per run - the entities it holds are moving, and a tree built last\n// run is a tree of where they all used to be.\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 index over nothing at all: Flatbush cannot be built to hold zero items, and there is no\n\t// answer to give from an empty index anyway.\n\tprivate index: Flatbush | undefined;\n\n\t// `entities` is anything shaped like a query result - `{ entityId, components }` - so a system's own\n\t// `queries.whatever` goes straight in. Entities without a transform are skipped rather than rejected: a\n\t// query that only *optionally* asks for one is a perfectly reasonable thing to hand over, and something with\n\t// no place in the world cannot be found by a search of the world.\n\tconstructor(entities: Iterable<{ entityId: number, components: EntityUpdateComponents }>) {\n\t\t// Boxes are collected up front because Flatbush has to be told how many items it will hold before any of\n\t\t// them are added, and how many that is only becomes clear once everything 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 query blocks generically as ComponentTypedArray since it cannot know what any given\n\t\t\t// game registered; narrow them to the concrete arrays the definitions allocate here, once, so neither\n\t\t\t// the searches below nor the game's filter needs a cast of its own.\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// A body only says which outline the transform's width and height describe, so an entity without one is\n\t\t\t// taken as the plain rectangle they already are. That keeps the index usable for anything with a place\n\t\t\t// in the world rather than only for 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, an entity with no area is kept: a search is a question about where\n\t\t\t// something is, and a thing with a position but no size still has an answer to it.\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 actually in the index, which is not how many were handed to it: anything without a\n\t// transform never made it in.\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 - two boxes that\n\t// share nothing but an edge are still found - so an exact question about where the edges fall is one for the\n\t// caller 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 rather than over its results is what keeps a rejected candidate from ever\n\t\t// reaching the results array in the first place.\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// The same search written from the middle out: everything within `reachX` / `reachY` of (x, y) on each axis.\n\t// This is the form most game searches are actually in - something looking a certain distance around itself -\n\t// and it saves every caller writing the same four sums.\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 there is none within `maxDistance`.\n\t//\n\t// This is a walk of the tree in distance order rather than a search followed by a sort: it settles on an\n\t// answer as soon as the nearest box is reached, and never measures the far side of the world at all. So a\n\t// game asking \"who is nearest\" should ask it this way rather than searching a box and sorting what comes\n\t// back - and `maxDistance` is what keeps it from walking the whole tree when the answer is that nobody is.\n\t//\n\t// Distance is to the entity's **box**, not to its centre, so a big thing is as near as its nearest edge -\n\t// which is what a ship closing on a station wants, and it makes `maxDistance` a range past the hull rather\n\t// than a range that a large enough target could sit outside of while overlapping the searcher.\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 - for a game\n\t// that wants a few to choose between rather than only the one.\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 that `maxResults` counts entities the game actually\n\t\t// wants: filtering after the fact would let a handful of rejects use up the whole allowance and return\n\t\t// nothing while a valid target sat just 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_CIRCLE, 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 { shapeHalfHeight, shapeHalfWidth } from '../math/shapes';\nimport type { PhysicsUpdateComponents } from '../components/registry';\nimport type { CollisionEntity, MovingEntity } from './collision';\n\ninterface Vector {\n\tx: number\n\ty: number\n}\n\n// Scratch, reused rather than allocated: the bounce runs once per collision per run, and the update is never\n// re-entered part way through - a run is one unbroken pass over the entities.\nconst NORMAL: Vector = { x: 0, y: 0 };\nconst SELF_HALF: Vector = { x: 0, y: 0 };\nconst OTHER_HALF: Vector = { x: 0, y: 0 };\n\n// Turns `self` around off `other`, having just run into it, and writes the new heading back into its velocity\n// block. `bounciness` is how much of the speed into the surface comes back out: 1 reflects it perfectly (a\n// head-on hit flips the velocity), 0 cancels it (the entity slides along the surface and stops pressing into\n// it), and values between lose that share of the speed on each bounce - see BouncinessComponent.\n//\n// Only `self` is touched. A collision is reported per entity as each one moves, so two entities that run into\n// each other each get their own call with the roles swapped and each bounces itself; a wall never moves, so it\n// never bounces at all. Writing the block is all it takes: it is shared memory, so the next run - and the main\n// thread - see the new heading immediately.\nexport function bounce<T extends PhysicsUpdateComponents>(self: MovingEntity<T>, other: CollisionEntity<T>): void {\n\tconst bounciness = self.components.bounciness;\n\tif(!bounciness) {\n\t\treturn;\n\t}\n\n\t// Nothing bounces off a sensor, and a sensor bounces off nothing: a sensor is felt only through onCollision,\n\t// never as a surface, so the two pass through each other and the velocity is left exactly as it was. The\n\t// pair is still reported - this is a `return` from the bounce, not from the collision - so a callback that\n\t// wants to react to the overlap still runs.\n\tif(isSensorBody(self.components.body) || isSensorBody(other.components.body)) {\n\t\treturn;\n\t}\n\n\tif(!collisionNormal(self, other, NORMAL)) {\n\t\treturn;\n\t}\n\n\tconst velocity = self.components.velocity;\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 this entity is already on its way\n\t// out - two that are still overlapping keep reporting the collision for as long as they overlap, and\n\t// reflecting a second time would turn it straight back in.\n\tconst into = velocityX * NORMAL.x + velocityY * NORMAL.y;\n\tif(into >= 0) {\n\t\treturn;\n\t}\n\n\t// The part of the velocity along the contact normal is reversed and scaled by how bouncy the entity is, and\n\t// the part along the surface is left alone, so a glancing hit stays glancing and a head-on one comes back.\n\t// At bounciness 1 the scale is 2, which is a mirror reflection that keeps the speed; at 0 it is 1, which\n\t// removes the normal component entirely and leaves the entity sliding along the surface.\n\tconst scale = (1 + bounciness[BOUNCINESS_INDEX]) * into;\n\tvelocity[VELOCITY_X_INDEX] = velocityX - scale * NORMAL.x;\n\tvelocity[VELOCITY_Y_INDEX] = velocityY - scale * NORMAL.y;\n}\n\n// Which way `self` should be pushed back off `other`, as a unit vector, or false when the two are exactly on\n// top of each other and there is no such direction.\nfunction collisionNormal<T extends PhysicsUpdateComponents>(self: MovingEntity<T>, other: CollisionEntity<T>, out: Vector): boolean {\n\tconst selfTransform = self.components.transform;\n\tconst otherTransform = other.components.transform;\n\tconst dx = selfTransform[TRANSFORM_X_INDEX] - otherTransform[TRANSFORM_X_INDEX];\n\tconst dy = selfTransform[TRANSFORM_Y_INDEX] - otherTransform[TRANSFORM_Y_INDEX];\n\n\tconst selfShape = shapeOf(self.components.body);\n\tconst otherShape = shapeOf(other.components.body);\n\n\t// Two circles touch at one point and the normal there is the line between their centres, exactly.\n\tif(selfShape === SHAPE_CIRCLE && otherShape === SHAPE_CIRCLE) {\n\t\tconst distance = Math.sqrt(dx * dx + dy * dy);\n\t\tif(distance === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\tout.x = dx / distance;\n\t\tout.y = dy / distance;\n\n\t\treturn true;\n\t}\n\n\t// Anything with a flat side is answered by the axis the two are *least* through each other on, which is the\n\t// face that was hit. A circle against a tall thin left wall overlaps it hugely in y and barely at all in x,\n\t// so x wins and the bounce is horizontal - which is the answer the centre-to-centre line above would have\n\t// got badly wrong, the wall's centre being a long way off up the screen.\n\thalfSize(selfTransform, selfShape, SELF_HALF);\n\thalfSize(otherTransform, otherShape, OTHER_HALF);\n\tconst overlapX = SELF_HALF.x + OTHER_HALF.x - Math.abs(dx);\n\tconst overlapY = SELF_HALF.y + OTHER_HALF.y - Math.abs(dy);\n\n\tif(overlapX < overlapY) {\n\t\tout.x = dx < 0 ? -1 : 1;\n\t\tout.y = 0;\n\t} else {\n\t\tout.x = 0;\n\t\tout.y = dy < 0 ? -1 : 1;\n\t}\n\n\treturn true;\n}\n\n// The body travels with every entity a collision reaches, but the type allows for one without it, so the shape\n// a size-only entity would collide as is the fallback.\nfunction shapeOf(body: Uint32Array | undefined): number {\n\treturn body ? body[BODY_SHAPE_INDEX] : SHAPE_RECTANGLE;\n}\n\n// Whether a body block is a sensor, allowing for the entity that arrived without one at all - a size-only entity\n// is a solid, not a sensor, so a missing body is false rather than a guess either way.\nfunction isSensorBody(body: Uint32Array | undefined): boolean {\n\treturn body !== undefined && body[BODY_SENSOR_INDEX] !== 0;\n}\n\n// How far the shape reaches from its centre along each axis, allowing for whatever it is rotated to - the same\n// numbers the library's own broadphase boxes an entity with.\nfunction halfSize(transform: Float32Array, shape: number, out: Vector): void {\n\tconst width = transform[TRANSFORM_WIDTH_INDEX];\n\tconst height = transform[TRANSFORM_HEIGHT_INDEX];\n\tconst angle = transform[TRANSFORM_ANGLE_INDEX];\n\n\tout.x = shapeHalfWidth(shape, width, height, angle);\n\tout.y = shapeHalfHeight(shape, width, height, angle);\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 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 CollisionFunction, type MovingEntity } from './collision';\nimport { bounce } from './bounce';\n\n// The per-run data object every physics update is handed, which is the base one plus the step counter the\n// interpolation component is stamped with. PhysicsSystem fills `tick` in from `addDataToWorld`, so a game that\n// subclasses the system to add data of its own widens this rather than replacing it - and **must call\n// `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, counted up once per run. Only ever compared for equality: it is the\n\t// publication stamp the interpolation block is read through, not a clock.\n\ttick: number\n\t// Whether to report this run's moves through POSITION_UPDATED_EVENT. Set by PhysicsSystem from whether\n\t// anything is actually listening, because the cost of the event is paid in the **worker** - an id per moved\n\t// entity pushed into an array, and that array structured-cloned back across the boundary - long before a\n\t// listener would have got the chance not to exist. Undefined means report, so an update driven by hand\n\t// behaves as it always did.\n\treportMoves?: boolean\n}\n\n// The event a move is reported through, emitted on the **system** on the main thread once the run that moved\n// them completes, carrying the ids of everything that moved in that run:\n//\n// physicsSystem.on(POSITION_UPDATED_EVENT, (entityIds: Array<number>) => {\n// for(const eid of entityIds) { ... }\n// });\n//\n// Nearly every entity a physics system owns moves on nearly every run, which rules out reporting the moves\n// one entity at a time: an event object apiece in the worker, cloned across the boundary, then an eid lookup\n// and an emit apiece on the main thread costs more, at a few thousand entities, than moving them did. So a\n// run's moves are reported as one array of ids on the system instead - see emitSystemEvent in the ECS.\n//\n// No position travels with the id. The transform is a SharedArrayBuffer block, so where the entity ended up\n// is already on the main thread the moment the worker writes it; a listener reads it off\n// `entity.components.transform` (or straight off the block) rather than being handed a copy of what it has.\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 has moved into another one, straight after its own move. Optional: an update\n\t// with no callback still sweeps, so leaving it off asks for movement that comes to rest against whatever is\n\t// in the way and decides nothing about it.\n\tonCollision?: CollisionFunction<C, T, W>\n\t// Extra components to hand the update - and so the collision callback - beyond the transform and velocity\n\t// physics itself needs. A game that wants to take damage on collision lists its health component here so\n\t// that block travels to the worker alongside the transform. They are optional per entity: an entity that\n\t// does not have one is still moved, and still collides, it just arrives without that block.\n\toptional?: Array<keyof C & string>\n}\n\n// What PhysicsSystem needs to know about an update function to set up the same components and queries the\n// function expects. Stamped on by createPhysicsUpdate so a game declares its collision setup in the one\n// module both its worker file and its main thread system import, with no chance of the two drifting apart.\nexport interface PhysicsUpdateMetadata<C extends ComponentMap> {\n\t// Whether the update needs the collidable query - everything with a transform and a body, whether it moves\n\t// or not. Always true for an update built by createPhysicsUpdate, which always sweeps; the bare\n\t// physicsUpdate carries no metadata at all and so 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 function a game runs physics through when it wants its entities to notice each other.\n// The result is a plain EntityUpdateFunction, so it goes to both backends the same way the bare physicsUpdate\n// does - handed to createComponentWorker in the game's worker file, and to PhysicsSystem as its\n// `updateFunction` for the in-process fallback:\n//\n// // game-physics-update.ts - imported by both the worker file and the main thread system\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 always comes to rest against what is in its way rather than\n// ending up inside it. The callback is on top of that and is optional - `createPhysicsUpdate()` with nothing\n// at all is movement that stops against things and says nothing about them, which is what a game wants for\n// terrain and walls. Movement that walks straight through everything is the bare physicsUpdate below.\n//\n// A callback cannot be sent to a worker (functions do not survive postMessage), which is why it is baked in\n// here rather than passed to PhysicsSystem: the worker file imports this module and gets the callback with it.\nexport function createPhysicsUpdate<\n\t// Constrained to a map that has the physics components in it rather than to any map at all, because the\n\t// blocks the update works on - `T` below - are the game's own components narrowed to the transform and\n\t// velocity it moves, so the map has to be able to see those two in there. Every game already spreads\n\t// physicsRegistry into its own, which is the same requirement PhysicsSystem makes.\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 and read back by each entity update that follows it, which is the whole reason it can be\n\t// a plain closure variable: a run is one unbroken pass - preRun, then every entity, then done - so there is\n\t// never a second run part way through this one to overwrite it.\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\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 two ends of the segment a renderer blends along are this\n\t\t\t// step's start and this step's end rather than positions from either side of it.\n\t\t\tstartInterpolationStep(interpolation, components.transform, world.elapsedTime);\n\n\t\t\t// preRun builds the tree before any entity is updated, so there is only nothing here for an update\n\t\t\t// that was called by hand without one. Nothing to sweep against then, and the move is the plain\n\t\t\t// integration.\n\t\t\tif(!broadphase) {\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// A bounce off what it hit is native, so an entity turns around on contact without the game writing a\n\t\t\t// callback for it: whether it bounces, and how much speed it keeps, is the bounciness component it\n\t\t\t// carries (or does not). It also decides how the move itself resolves - see below - so it is read\n\t\t\t// before the move rather than after it.\n\t\t\tconst bouncing = components.bounciness !== undefined;\n\n\t\t\t// Where the entity may actually get to is worked out here, off the blocks rather than in them: the\n\t\t\t// position is only written once the answer is known, so an entity is never inside another one - not\n\t\t\t// even for the moment it would take to push it back out.\n\t\t\t//\n\t\t\t// The move is resolved whether or not the game gave a callback. Coming to rest against what is in the\n\t\t\t// way is what physics *does* about a collision; `onCollision` is what the game does about it, and\n\t\t\t// plenty of games want the first without the second. Movement that ignores everything around it is\n\t\t\t// the bare physicsUpdate below rather than an option here.\n\t\t\t//\n\t\t\t// An entity that is not bouncing is let slide along a single axis where its diagonal is blocked, so it\n\t\t\t// comes off a corner still moving rather than sticking to it. A bouncing one is not: it is about to\n\t\t\t// turn around off the face it hit, so skating along that face is the wrong answer for it.\n\t\t\tconst moved = broadphase.resolveMove(self, moveX, moveY, !bouncing);\n\t\t\tmove(entityId, components.transform, moved.moveX, moved.moveY, callbacks, world.reportMoves);\n\t\t\t// Straight after the move and before any callback, so the stamp covers exactly the pair physics itself\n\t\t\t// produced. A callback that goes on to write the transform is a move the game made rather than one\n\t\t\t// this step did, and it is blended towards on the next frame the same way a teleport is.\n\t\t\tfinishInterpolationStep(interpolation, world.tick);\n\n\t\t\tif(!bouncing && !onCollision) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Collisions are handled here, straight after the entity has been put down, so both the bounce and any\n\t\t\t// callback act on where it has actually ended up rather than where it was - and the flipped velocity is\n\t\t\t// applied by the next run's move. Only the entity that moved is touched; whatever it hit gets its own\n\t\t\t// turn when it moves, or never, if it is something the system does not move.\n\t\t\tbroadphase.forEachOverlapping(self, other => {\n\t\t\t\tif(bouncing) {\n\t\t\t\t\tbounce(self, other);\n\t\t\t\t}\n\t\t\t\tif(onCollision) {\n\t\t\t\t\tonCollision(world, self, other, queries, callbacks);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Then whatever the move was stopped *against*, which the overlap check above cannot find by design:\n\t\t\t// the entity was put down touching it rather than through it. The two lists never share an entry, so\n\t\t\t// nothing here has already bounced or had its callback.\n\t\t\tfor(const other of moved.blocking) {\n\t\t\t\tif(bouncing) {\n\t\t\t\t\tbounce(self, other);\n\t\t\t\t}\n\t\t\t\tif(onCollision) {\n\t\t\t\t\tonCollision(world, self, other, queries, callbacks);\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 whether or not anything is going to be told about what it\n\t\t\t\t// found, 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 is the one hook that sees every entity at once, which makes it the only place the tree can be\n\t// built from a single consistent moment - before any of this run's movement has happened.\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// Shared update logic used by both ComponentSystem backends: the main-thread ComponentWebWorker runs it\n// directly (PhysicsSystem passes it as `updateFunction`), while a game's worker file imports it and hands it\n// to createComponentWorker. Keeping it in one place means both paths run identical logic.\n//\n// This is movement only, and the *only* way to get movement with no collision detection at all: it walks an\n// entity straight through anything in its way, which is what a game wants for something that has no business\n// noticing the world - a drifting particle, a camera, a projectile that only its own system cares about. A\n// game that wants entities stopped by each other, with or without a callback about it, builds its update with\n// createPhysicsUpdate above instead, which sweeps around this same move.\nexport const physicsUpdate: EntityUpdateFunction<PhysicsComponents, PhysicsUpdateComponents, PhysicsWorld> = (world, entityId, components, queries, callbacks) => {\n\t// elapsedTime is in milliseconds (what BaseWorld#update is driven with), while velocity is in world units\n\t// per second - so convert before integrating.\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 has to happen *before* the move: where the entity is standing now becomes\n// the `prev` the next frame's render position is blended out of, along with how much simulated time the segment\n// between the two is going to cover.\n//\n// The duration is published rather than left to be assumed, because a run does not always cover one step: one\n// that comes back late leaves more than a step's worth of time banked and the next one takes two at once. A\n// renderer dividing that segment by the step instead of by what it really covers draws it at double speed.\n//\n// Nothing is written for an entity without the component, which is what makes interpolation opt-in per entity\n// rather than a cost every world pays.\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// And the half that has to happen after it. Written unconditionally - outside `move`'s \"did not go anywhere\"\n// early return - so an entity pressed against a wall publishes a step where `prev` equals where it still is,\n// rather than going quiet and leaving a renderer blending against whatever it was doing before it stopped.\n//\n// The tick goes last and through storeFloat32, which is an `Atomics.store` through an Int32Array view of the\n// same memory: a release store, so a reader that sees this tick is guaranteed to also see the `prev` and the\n// transform that belong with it. 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 a move that has already been decided on - by the integration above, or by what a sweep allowed of it -\n// and reports where the entity ended up. Because the blocks live in a SharedArrayBuffer, writing them here is\n// immediately visible through `entity.components.transform` on the main thread.\n//\n// This is the one place the position is written, and it is only reached once the final position is known: the\n// distance is worked out on plain numbers first so that nothing ever reads a transform mid-decision.\n//\n// The move is applied with addAtomicFloat32 rather than `+=`: the position is shared memory that another\n// thread (a game's own system, or a later physics system) may be adding to at the same moment, and a plain\n// read-modify-write would silently drop one of the two moves. Its `max` cap is not wanted here - physics\n// does not bound how far something may travel - so it is passed Infinity, leaving a plain atomic add.\n//\n// A move that did not go anywhere is not reported, which is what keeps a world of still entities quiet - and an\n// entity pressed up against something it cannot move past reports nothing at all rather than a change per run.\n// An entity that did move is reported once however many axes it moved along: a listener keyed off position - a\n// spatial index, a minimap, a sprite - wants the place rather than the axis, and it reads the whole transform\n// out of shared memory anyway.\n//\n// `reportMoves` is what makes even that skippable. It is not a saving on this line - it is a saving on the id\n// that would have been pushed into the run's event array, and on that whole array being cloned back across the\n// worker boundary once a step. A game reading positions through the interpolation component wants nothing to\n// do with it: the event fires at the physics rate, which is the rate it is drawing *around*. See\n// PhysicsSystem, which sets this from whether anything is actually listening.\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// The id on its own: the block above is what the main thread reads the position out of, and it is the same\n\t// memory, so there is nothing here worth sending it a copy of.\n\tcallbacks.emitSystemEvent(POSITION_UPDATED_EVENT, entityId);\n}\n\n// The one callback move reaches for, narrowed to the event physics reports. ComponentSystemCallbacks takes\n// any event name, which says nothing about what to listen for - so the moves are handed over through this\n// instead.\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// How long a physics step covers when a game does not say, in the milliseconds `BaseWorld#update` is driven\n// with. 20Hz rather than a frame: collision is by a distance the whole of it, and running it three times less\n// often is three times less of the frame spent on it, for a simulation that is no less correct - only coarser.\n// What it costs is smoothness, and that is what InterpolationSystem is for.\n//\n// `deltaBetweenRuns: 0` puts it back on every frame, which is what a game with very fast entities and no\n// interpolation wants: a move is swept where it *ends*, so a longer step is a longer move, and a move longer\n// than the thing in its way is thick goes 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\n\t// URL(...))` call to be written out in the game's own source, so the game supplies the file:\n\t//\n\t// // physics.worker.ts\n\t// import { createComponentWorker } from '@daneren2005/shared-memory-ecs';\n\t// import { physicsUpdate } from '@daneren2005/shared-memory-physics';\n\t// createComponentWorker(self, physicsUpdate);\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 ComponentWebWorker even when a getWorker is supplied. Defaults to true when no\n\t// getWorker was given, since there is then nothing to run the update on.\n\tforceMainThread?: boolean\n\t// The update to run, defaulting to plain movement that walks through anything in its way. A game that\n\t// wants its entities stopped by each other builds one with createPhysicsUpdate and passes the *same*\n\t// function its worker file hands to createComponentWorker, so both backends behave identically.\n\t// createPhysicsUpdate stamps what it needs onto the function, so `optional` and the collidable query below\n\t// come across with it and do not have to be repeated here.\n\tupdateFunction?: EntityUpdateFunction<C, T, W> & { physics?: PhysicsUpdateMetadata<C> }\n\t// Extra components to send along with each entity's transform and velocity, for an update function built\n\t// by hand rather than through createPhysicsUpdate. Overrides what the update function declared.\n\toptional?: Array<keyof C & string>\n\t// Whether to gather the collidable query - every entity with a transform, whether it moves or not - and\n\t// send it along for the update's collision detection. Defaults to whatever the update function declared,\n\t// so it is on for any update built by createPhysicsUpdate (which always sweeps, callback or not) and off\n\t// for the bare physicsUpdate, which is movement that notices nothing.\n\tcollision?: boolean\n\t// Whether each run reports what moved through POSITION_UPDATED_EVENT. **Defaults to whether anything is\n\t// listening**, which is nearly always the right answer and is checked per run, so a game that adds or drops\n\t// a listener needs to do nothing here.\n\t//\n\t// It is worth a setting at all because the bill is paid in the worker: an id per moved entity pushed into\n\t// the run's event array, and that array structured-cloned back across the boundary. Nearly every entity\n\t// moves on nearly every run, so at a few thousand entities that is a few thousand ids a step for a listener\n\t// that may not exist. Force it `true` only for a listener attached somewhere this system cannot see.\n\treportMoves?: boolean\n\t// Narrows which of the entities this system *moves* to a subset, run on the main thread as the entity list\n\t// for the worker is built. Every entity with a transform and a velocity is moved by default; a game that\n\t// wants to spread one simulation across several workers gives each system the same update but a filter that\n\t// picks a different shard - `entity => entity.eid % workers === n` - so each worker moves its own slice. The\n\t// collidable query below is *not* filtered, so every shard still sweeps against the whole world; likewise an\n\t// extra query in `queries` is gathered whole, so a shard that only moves a slice can still read all of its\n\t// neighbours out of shared memory.\n\tfilter?: (entity: BaseEntity<C>) => boolean\n\t// Extra queries to gather and send to the worker alongside the collidable one, for an update that needs to\n\t// see more of the world than the entity it is moving - a flocking update reading every boid's position to\n\t// steer one, say. Each is a list the update reads by name off its `queries` argument, and it is gathered\n\t// whole regardless of the `filter` above, so a sharded system still sees every entity the query matches.\n\t// Merged with the collidable query rather than replacing it, so a collision update can ask for both.\n\tqueries?: { [key: string]: ComponentSystemQuery<C> }\n}\n\n// Moves every entity that has both a transform and a velocity, and (when the update function detects\n// collisions) reports the entities that overlap to the game's callback. Generic over `C`, the game's full\n// component map, so a game's own world passes straight in; over `T`, the blocks its update function touches,\n// which is wider than transform + velocity once a game adds `optional` components of its own; and over `W`,\n// the per-run data object, for a game that subclasses this to add more through `addDataToWorld`.\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// Counted up once per run and stamped onto every interpolated entity, so a renderer can tell a position it\n\t// has already blended from one physics has just replaced. Nothing does arithmetic with it, so it only has\n\t// to change - and a Float32 holds integers exactly to 2^24, which at a 50ms step is over nine days of\n\t// continuous simulation before it stops incrementing in the block.\n\ttick = 0;\n\n\t// Undefined means \"whenever something is listening\", which is what addDataToWorld works out per run.\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// An entity reads its own collide category and mask before it searches, so the body block has to travel\n\t\t// with the entities this system moves as well as with the ones they can run into - but only when there is\n\t\t// collision detection to read it, since otherwise it is a block per moving entity for nothing.\n\t\t//\n\t\t// Bounciness rides along on the same condition: it is only ever read for a moving entity that has run into\n\t\t// something, which is the collision path, and it turns that entity around off what it hit. It costs\n\t\t// nothing for the entities that do not bounce - the ECS only puts a component in the message when the\n\t\t// entity actually holds it - so this only widens what *may* be read, not what every entity carries.\n\t\t//\n\t\t// The interpolation block goes along unconditionally, because the update has to publish into it for any\n\t\t// entity that has one and there is no flag that says which entities those are. It costs nothing for the\n\t\t// ones that do not: the ECS only puts a component in the message when the entity actually holds it.\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\tconst movingOptional = extraOptional.length ? [...extraOptional, ...optional] : optional;\n\n\t\t// Collision is between anything with a transform and a body, not only the entities this system moves, so\n\t\t// that a ship can hit a station that has no velocity of its own. It is only gathered when something is\n\t\t// actually going to look at it, since it means shipping a block for every entity in the world. A game's\n\t\t// own extra queries (see `queries` above) are merged on top rather than instead, so an update can ask for\n\t\t// both the collidable set and one of its own - and neither is narrowed by the shard `filter`, so every\n\t\t// worker still sees the whole world it is searching even when it only moves a slice of it.\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', ...optional] 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\t// A fixed step by default rather than every frame - see DEFAULT_PHYSICS_STEP_MS.\n\t\t\tdeltaBetweenRuns: options.deltaBetweenRuns ?? DEFAULT_PHYSICS_STEP_MS,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// Movement needs both, so an entity with only one of them is not in the system at all - though it can\n\t\t\t// still be collided with through the query above.\n\t\t\trequired: ['transform', 'velocity'],\n\t\t\toptional: movingOptional,\n\t\t\t// Narrows the moved entities to a shard when a game spreads one simulation across several workers; left\n\t\t\t// off, every entity with a transform and a velocity is moved.\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 ever reached when a getWorker was supplied: without one forceMainThread defaults to true, so\n\t\t\t// the base class never asks for a worker.\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, which is what the interpolation block is published under, and says\n\t// whether this run should bother reporting what moved.\n\t//\n\t// A game that subclasses this to attach data of its own **must call `super.addDataToWorld(world)`**: this is\n\t// an ordinary override, so replacing it without the super call leaves `tick` 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 rather than once at construction, so a game is free to attach and drop the listener\n\t\t// whenever it likes. It costs one property read on a thread that is about to sweep every entity in the\n\t\t// world, and it saves the worker building and cloning an id per moved entity when nobody wanted them.\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.\n//\n// render = prev + (current - prev) * alpha\n//\n// **Every position this writes is one the simulation actually produced** - somewhere on a segment between two\n// real positions - which is what it buys over guessing forward from the current position and a velocity. An\n// entity that stopped against a wall is drawn easing into the wall and stopping, because that is what happened;\n// one that turned a corner is drawn turning the corner, rather than continuing into the old heading for a frame\n// and then being snapped back. There is nothing to tune and no case where it draws something the entity did\n// not do.\n//\n// The cost is one step of latency, always: what is on screen is where the world was a step ago, not where it is\n// now. At a 50ms step that is 50ms behind on every entity, in exchange for never being wrong.\n//\n// ## How far along the segment to draw\n//\n// Two rules, and between them they are the whole of it:\n//\n// 1. **Advance by a frame's worth of the segment, every frame.** A segment covers `duration` milliseconds of\n// simulated time and the frame just drawn covered `elapsedTime` of it, so the render position moves\n// `elapsedTime / duration` of the way along. That is the same size every frame however the frames and the\n// steps happen to line up, which is what smooth means.\n// 2. **Start the next segment when it lands, and drop whatever was left of the old one.** It is only ever a\n// small remainder - the cap below means a segment is never overshot - and it cannot be carried: see the\n// note at the reset for why a deficit is the one thing this arithmetic cannot draw.\n//\n// ## Why none of this is read off the physics system\n//\n// The tempting clock is the physics system's own accumulator: `currentDelta / deltaBetweenRuns` sweeps 0 -> 1\n// once per step with nothing to keep in sync. It is also **wrong**, and wrong in exactly the way that makes a\n// 60fps page stutter whenever a physics run takes longer than a frame.\n//\n// That accumulator resets when a run is *posted*. This block changes when that run *lands*, and on a worker\n// thread those are different moments. A run posted with 14ms of leftover and taking 30ms to come back leaves\n// one whole frame where the accumulator says \"0.3 of the way into the new step\" while the block still holds the\n// step before it - so the entity is drawn 0.3 along a segment it was drawn 0.96 along last frame. Backwards,\n// then a lurch forward when the step finally lands, on every single step.\n//\n// So the pacing is driven by what has actually arrived, and the segment's length is published with it rather\n// than assumed to be the step: a run that comes back late leaves more than a step's worth of time banked, and\n// the next one covers two steps at once. A slow physics run then costs latency and nothing else, which is the\n// entire point - a step drawn late but at the right speed looks identical to one drawn on time.\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 very entity on\n\t// another thread right now, and a `prev` from after the move paired with a transform from before it draws\n\t// the entity walking backwards along a segment it has already finished.\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. The render position is left exactly as it was\n\t// for this one frame rather than being drawn from a mismatched pair: an entity that holds still for 16ms is\n\t// invisible, and one that jumps to a position neither end of the segment agrees with is not.\n\tif(before !== after) {\n\t\treturn;\n\t}\n\n\t// No segment to be part way along: an entity physics has never moved - a station, a wall - or one whose\n\t// first step has not landed yet. Where it is is where to draw it.\n\t//\n\t// Progress is left *finished* rather than at zero, because that is what it means: there is nothing left to\n\t// draw of the nothing that has happened. It is also what makes the first real step start from the right\n\t// place - the rule below takes one whole segment off whatever was banked, and taking it off zero would open\n\t// with a full segment of debt the entity could never work off.\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, so start again at the beginning of it - which is where the entity has just been drawn\n\t\t// anyway, because the new segment's `prev` is the old one's end.\n\t\t//\n\t\t// Whatever was left undrawn of the old segment is **dropped** rather than carried, and that is the whole\n\t\t// decision here. Carrying it is only possible as a *deficit* - the cap below means progress can never\n\t\t// arrive above 1 - and a deficit cannot be drawn: there is no third position to blend back into, so a\n\t\t// negative progress just pins the entity at `prev`, which is further forward than where it was. The debt\n\t\t// then never gets paid, grows by the same amount every step, and the entity lurches a whole segment each\n\t\t// time. Dropping it costs a fraction of a frame on the step that lands early, and costs nothing at all\n\t\t// when the step and the frame divide into each other - which at a 50ms step and 60fps they do.\n\t\tprogress = 0;\n\t\tinterpolation[INTERPOLATION_SYNCED_TICK_INDEX] = after;\n\t}\n\n\t// Capped at the end of the segment, which is what absorbs a slow physics run. While waiting for the next\n\t// step the entity sits at the newest position the simulation actually produced, and the time spent waiting\n\t// is **dropped** rather than banked - so when the step does land there is no debt to spend all at once and\n\t// the next frame carries on at the same speed as every other. A worker that is consistently 30ms late costs\n\t// 30ms of latency and nothing else; only *variance* in how late it is can be seen at all.\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// As on PhysicsSystem, and defaulted the other way round: this runs on the main thread unless a game asks\n\t// otherwise, since a render position read one frame after it was computed is a render position one frame\n\t// stale, which is the latency this system exists to keep down.\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 not wired to the physics system at all**, and that is deliberate rather than convenient. Everything it\n// needs - the two positions, how much simulated time lies between them, and whether that pair is new - is in the\n// block, published by whichever run wrote it. Pacing off the physics system instead would mean pacing off when a\n// run was *posted*, and on a worker thread that is not when its results arrive; see interpolation-update.ts for\n// what that costs.\n//\n// So it takes no configuration, and it follows a step the game retunes mid-flight with nothing told to it:\n//\n// world.addSystem(new PhysicsSystem(world, { getWorker }));\n// world.addSystem(new InterpolationSystem(world));\n//\n// Ordering against the physics system is a preference rather than a requirement - going after it means a step\n// is picked up on the frame it happened rather than the one after, which is one frame of latency and nothing\n// else. Pause and `timeScale` need no code either: `BaseWorld#runUpdate` skips every system while paused and\n// scales the elapsed time it hands them, so the pacing stops and slows with the simulation 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 entire point of it.\n\t\t\tdeltaBetweenRuns: 0,\n\t\t\tfirstRun: options.firstRun,\n\n\t\t\t// A velocity is never read - the blend is between two positions the simulation already produced - so\n\t\t\t// something with no velocity of its own is in this system on exactly the same terms as a ship, and gets\n\t\t\t// a render position that tracks its transform.\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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"}