• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excaliburjs / Excalibur / 18690088517

21 Oct 2025 04:03PM UTC coverage: 88.647% (+0.05%) from 88.6%
18690088517

push

github

web-flow
fix: ECS Queries incorrect when using not conditions (#3540)

Fixes an issue discovered while trying to do some perf optimization, turns out adding a component can now disqualify entities from queries! And removing a component can qualify it!

## Changes:

- Deprecates `ex.TagQuery` these are now redundant and tags are handled by `ex.Query`
- Adds new integration optimization `physics.integration.onScreenOnly = false`
- Adds granular integration optimization `MotionComponent.integration.onScreenOnly = false`

5183 of 7067 branches covered (73.34%)

61 of 63 new or added lines in 6 files covered. (96.83%)

14289 of 16119 relevant lines covered (88.65%)

24602.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.12
/src/engine/Collision/MotionSystem.ts
1
import type { Entity, Query, World } from '../EntityComponentSystem';
2
import { SystemPriority } from '../EntityComponentSystem';
3
import { MotionComponent } from '../EntityComponentSystem/Components/MotionComponent';
4
import { TransformComponent } from '../EntityComponentSystem/Components/TransformComponent';
5
import { System, SystemType } from '../EntityComponentSystem/System';
6
import { BodyComponent } from './BodyComponent';
7
import { CollisionType } from './CollisionType';
8
import { EulerIntegrator } from './Integrator';
9
import type { PhysicsWorld } from './PhysicsWorld';
10

11
export class MotionSystem extends System {
246✔
12
  static priority = SystemPriority.Higher;
13

14
  public systemType = SystemType.Update;
1,369✔
15
  private _physicsConfigDirty = false;
1,369✔
16
  query: Query<typeof TransformComponent | typeof MotionComponent>;
17
  constructor(
18
    public world: World,
1,369✔
19
    public physics: PhysicsWorld
1,369✔
20
  ) {
21
    super();
1,369✔
22
    this.query = this.world.query({
1,369✔
23
      components: {
24
        all: [TransformComponent, MotionComponent]
25
      },
26
      tags: {
27
        not: this.physics.config.integration.onScreenOnly ? ['ex.offscreen'] : []
1,369!
28
      }
29
    });
30

31
    physics.$configUpdate.subscribe(() => {
1,369✔
32
      this._physicsConfigDirty = true;
684✔
33
    });
34
  }
35

36
  update(elapsed: number): void {
37
    let transform: TransformComponent;
38
    let motion: MotionComponent;
39
    const entities = this.query.entities;
1,804✔
40
    const config = this.physics.config;
1,804✔
41
    const substep = config.substep;
1,804✔
42

43
    for (let i = 0; i < entities.length; i++) {
1,804✔
44
      transform = entities[i].get(TransformComponent);
2,354✔
45
      motion = entities[i].get(MotionComponent);
2,354✔
46

47
      if (motion.integration.onScreenOnly && entities[i].hasTag('ex.offscreen')) {
2,354!
NEW
48
        continue;
×
49
      }
50

51
      const optionalBody = entities[i].get(BodyComponent);
2,354✔
52
      if (this._physicsConfigDirty && optionalBody) {
2,354✔
53
        optionalBody.updatePhysicsConfig(this.physics.config.bodies);
311✔
54
      }
55

56
      if (optionalBody?.isSleeping) {
2,354!
57
        continue;
×
58
      }
59

60
      const totalAcc = motion.acc.clone();
2,354✔
61
      if (optionalBody?.collisionType === CollisionType.Active && optionalBody?.useGravity) {
2,354!
62
        totalAcc.addEqual(this.physics.config.gravity);
329✔
63
      }
64

65
      // capture old transform of this entity and all of its children so that
66
      // any transform properties that derived from their parents are properly captured
67
      if (!entities[i].parent) {
2,354✔
68
        this.captureOldTransformWithChildren(entities[i]);
2,331✔
69
      }
70

71
      // Update transform and motion based on Euler linear algebra
72
      EulerIntegrator.integrate(transform, motion, totalAcc, elapsed / substep);
2,354✔
73
    }
74
    if (this._physicsConfigDirty) {
1,804✔
75
      this._physicsConfigDirty = false;
516✔
76
      this.query = this.world.query({
516✔
77
        components: {
78
          all: [TransformComponent, MotionComponent]
79
        },
80
        tags: {
81
          not: this.physics.config.integration.onScreenOnly ? ['ex.offscreen'] : []
516!
82
        }
83
      });
84
    }
85
  }
86

87
  captureOldTransformWithChildren(entity: Entity) {
88
    entity.get(BodyComponent)?.captureOldTransform();
3,248✔
89

90
    for (let i = 0; i < entity.children.length; i++) {
3,248✔
91
      this.captureOldTransformWithChildren(entity.children[i]);
917✔
92
    }
93
  }
94
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc