Matter physics collisions sometimes resulting in NaN velocity values

So I have no idea what’s happening here. I’m using matter physics, and I have a simple 2D basketball shooter game I’m building. The rim of the hoop has ‘2 sides’, left and right, that the ball can collide with and bounce around before dropping down. Below the rim is a sensor zone that, if the ball passes through, a point is scored.

However, in some situations, my ball will collide with the rim and then just disappear. Using debug mode I see that the velocity, position, and angle values are all NaN. Just console logging out values in my update(), the last logged velocity values are NaN before the ball disappears.

More often than not this happens when I launch with barely enough velocity to get just over the rim and the ball sort of sits there for a second. But it’s happened in seemingly normal circumstances too.

Here’s my collisionstart event, for reference:

        this.matter.world.on('collisionstart', (event) => {
            event.pairs.forEach((pair) => {
                const { bodyA, bodyB } = pair;
                const ballBody = this.throwable.body;

                if (
                    (bodyA.label === 'leftGoalBound' ||
                        bodyA.label === 'rightGoalBound') &&
                    bodyB === ballBody
                ) {
                    const ballBottom =
                        ballBody.position.y + ballBody.circleRadius;
                    if (
                        ballBody.velocity.y > 0 &&
                        ballBottom <= this.leftGoalBound.position.y - 10
                    ) {
                        pair.isActive = true;
                    } else {
                        pair.isActive = false;
                    }
                }

                if (bodyA.label === 'goalSensor' && bodyB === ballBody) {
                    const ballBottom =
                        ballBody.position.y + ballBody.circleRadius;
                    const ballX = ballBody.position.x;
                    const leftRimX = this.leftGoalBound.position.x;
                    const rightRimX = this.rightGoalBound.position.x;

                    if (
                        ballBody.velocity.y > 0 &&
                        ballBottom <= this.goalSensor.position.y &&
                        ballX > leftRimX &&
                        ballX < rightRimX &&
                        !this.hasScored
                    ) {
                        this.handleHoopScore();
                    }
                }

                const didBallHitGround =
                    bodyA.label === 'ground' && bodyB === ballBody;
                if (didBallHitGround) {
                    this.resetBall(this.throwable);
                }
            });
        });

If you need more code from me, let me know. Here’s the entire create() function, if interested, but I feel the issue is in collisions themselves?

    create() {
        const { height, width } = this.game.config;

        // Add custom debug text objects
        this.debugText = this.add
            .text(16, height / 2, '', {
                fontSize: '16px',
                fill: '#000',
            })
            .setOrigin(0, 0.5);

        this.matter.world.setBounds(
            0,
            0,
            width,
            height,
            32,
            false,
            false,
            false,
            true
        ); // disable top, left, right boundaries

        this.background = this.add
            .tileSprite(0, 0, width, height, 'global-1', 'background')
            .setOrigin(0, 0)
            .setAlpha(0.6);
        setHeightAndScale(this.background, height);

        this.ground = this.matter.add.rectangle(width / 2, height, width, 10, {
            isStatic: true,
            label: 'ground',
        });

        this.goal = this.add.sprite(
            width / 2,
            600,
            'global-1',
            this.roundConfig.goal
        );
        setHeightAndScale(this.goal, 500);

        this.leftGoalBound = this.matter.add.circle(width / 2 - 60, 480, 1, {
            isStatic: true,
            label: 'leftGoalBound',
            restitution: 0.4,
        });
        this.rightGoalBound = this.matter.add.circle(width / 2 + 60, 480, 1, {
            isStatic: true,
            label: 'rightGoalBound',
            restitution: 0.4,
        });

        this.goalSensor = this.matter.add.rectangle(
            width / 2,
            this.config.goalSettings.sensorHeight,
            this.config.goalSettings.sensorWidth,
            10,
            {
                isStatic: true,
                isSensor: true,
                label: 'goalSensor',
            }
        );

        this.throwable = this.matter.add.sprite(
            width / 2,
            height - 50,
            'global-1',
            this.roundConfig.throwable
        );
        setHeightAndScale(this.throwable, 150);
        this.throwable.setCircle();
        this.throwable.setBounce(0.6);
        this.throwable.setInteractive({ useHandCursor: true });
        this.throwable.setVelocity(0, 0);

        this.matter.add.mouseSpring({ length: 1, stiffness: 1 });

        this.matter.world.setGravity(0, 5);

        this.scoreText = this.add
            .text(width / 2, 80, `SCORE`, {
                font: '300 32px Poppins',
                fill: '#000',
            })
            .setOrigin(0.5, 0.5);

        this.scoreValueText = this.add
            .text(width / 2, 140, `${this.score}`, {
                fontFamily: 'Poppins',
                fontSize: '64px',
                fill: '#000',
            })
            .setOrigin(0.5, 0.5);

        this.roundText = this.add
            .text(width / 2 - 100, 220, `ROUND ${this.round}`, {
                fontFamily: 'Poppins',
                fontSize: '24px',
                fill: '#000',
            })
            .setOrigin(0.5, 0.5);

        this.timerText = this.add
            .text(width / 2 + 100, 220, `:${this.roundConfig.roundTime}`, {
                fontFamily: 'Poppins',

                fontSize: '64px',
                fill: '#000',
            })
            .setOrigin(0.5, 0.5);

        this.countdownText = this.add
            .text(width / 2, height / 2, '', {
                fontFamily: 'Poppins',

                fontSize: '64px',
                fill: '#ff0000',
            })
            .setOrigin(0.5, 0.5);

        this.input.on('pointermove', (pointer) => {
            if (!this.isGameRunning || !this.isDown) return;

            const distance = Phaser.Math.Distance.Between(
                this.startX,
                this.startY,
                pointer.x,
                pointer.y
            );

            const maxDragDistance =
                height / this.config.launchSettings.dragDistanceDividend;

            if (distance > maxDragDistance) {
                this.resetBall(this.throwable);
                this.isDown = false;
            }
        });

        this.throwable.on('pointerdown', (pointer) => {
            if (this.isGameRunning) {
                this.isDown = true;
                this.startX = pointer.x;
                this.startY = pointer.y;
                this.throwable.setStatic(false);
            }
        });

        this.throwable.on('pointerup', () => {
            if (!this.isGameRunning || !this.isDown) return;
            this.isDown = false;

            let velocityX = this.throwable.body.velocity.x * 0.5;
            let velocityY = this.throwable.body.velocity.y;

            velocityX = Phaser.Math.Clamp(
                velocityX,
                -this.config.launchSettings.maxVelocity,
                this.config.launchSettings.maxVelocity
            );
            velocityY = Phaser.Math.Clamp(
                velocityY,
                -this.config.launchSettings.maxVelocity,
                this.config.launchSettings.maxVelocity
            );

            const wasBallLaunched =
                Math.abs(velocityX) >= this.config.launchSettings.minVelocity ||
                Math.abs(velocityY) >= this.config.launchSettings.minVelocity;
            if (!wasBallLaunched) {
                this.resetBall(this.throwable);
                return;
            } else {
                this.throwable.setVelocity(velocityX, velocityY);
                this.throwable.disableInteractive();

                this.scaleTween = this.tweens.add({
                    targets: this.throwable,
                    scaleX: 0.4,
                    scaleY: 0.4,
                    duration: 500,
                    ease: 'Power1',
                    persist: true,
                    onComplete: () => {
                        this.scaleTween.stop();
                    },
                });
            }
        });

        this.matter.world.on('collisionstart', (event) => {
            event.pairs.forEach((pair) => {
                const { bodyA, bodyB } = pair;
                const ballBody = this.throwable.body;

                if (
                    (bodyA.label === 'leftGoalBound' ||
                        bodyA.label === 'rightGoalBound') &&
                    bodyB === ballBody
                ) {
                    const ballBottom =
                        ballBody.position.y + ballBody.circleRadius;
                    if (
                        ballBody.velocity.y > 0 &&
                        ballBottom <= this.leftGoalBound.position.y - 10
                    ) {
                        pair.isActive = true;
                    } else {
                        pair.isActive = false;
                    }
                }

                if (bodyA.label === 'goalSensor' && bodyB === ballBody) {
                    const ballBottom =
                        ballBody.position.y + ballBody.circleRadius;
                    const ballX = ballBody.position.x;
                    const leftRimX = this.leftGoalBound.position.x;
                    const rightRimX = this.rightGoalBound.position.x;

                    if (
                        ballBody.velocity.y > 0 &&
                        ballBottom <= this.goalSensor.position.y &&
                        ballX > leftRimX &&
                        ballX < rightRimX &&
                        !this.hasScored
                    ) {
                        this.handleHoopScore();
                    }
                }

                const didBallHitGround =
                    bodyA.label === 'ground' && bodyB === ballBody;
                if (didBallHitGround) {
                    this.resetBall(this.throwable);
                }
            });
        });

        this.startRound();
    }