function dispatchEvent(normalizedEvents) {
	let handled = false;

	while (normalizedEvents.length > 0) {
		// keep a reference to the last item
		const pointer = normalizedEvents.pop();
		// and put it back into our cache
		T_POINTERS.push(pointer);

		// Do not fire older touch events (not required for PointerEvent type)
		if (
			pointer.isNormalized === true &&
			typeof pointer.event.timeStamp !== "undefined"
		) {
			if (pointer.event.timeStamp < lastTimeStamp) {
				continue;
			}
			lastTimeStamp = pointer.event.timeStamp;
		}

		currentPointer.pos.set(pointer.gameWorldX, pointer.gameWorldY);
		currentPointer.setSize(pointer.width, pointer.height);

		// trigger a global event for pointer move
		if (POINTER_MOVE.includes(pointer.type)) {
			pointer.gameX = pointer.gameLocalX = pointer.gameScreenX;
			pointer.gameY = pointer.gameLocalY = pointer.gameScreenY;
			eventEmitter.emit(POINTERMOVE, pointer);
		}

		// fetch valid candiates from the game world container
		let candidates = game.world.broadphase.retrieve(
			currentPointer,
			game.world._sortReverseZ,
		);

		// add the main game viewport to the list of candidates
		candidates = candidates.concat([game.viewport]);

		for (
			let c = candidates.length, candidate;
			c--, (candidate = candidates[c]);
		) {
			if (eventHandlers.has(candidate) && candidate.isKinematic !== true) {
				const handlers = eventHandlers.get(candidate);
				const region = handlers.region;
				const ancestor = region.ancestor;
				const bounds = region.getBounds();
				let eventInBounds = false;

				if (region.isFloating === true) {
					pointer.gameX = pointer.gameLocalX = pointer.gameScreenX;
					pointer.gameY = pointer.gameLocalY = pointer.gameScreenY;
				} else {
					pointer.gameX = pointer.gameLocalX = pointer.gameWorldX;
					pointer.gameY = pointer.gameLocalY = pointer.gameWorldY;
				}

				// adjust gameLocalX to specify coordinates
				// within the region ancestor container
				if (typeof ancestor !== "undefined") {
					const parentBounds = ancestor.getBounds();
					pointer.gameLocalX = pointer.gameX - parentBounds.x;
					pointer.gameLocalY = pointer.gameY - parentBounds.y;
				}

				eventInBounds = bounds.contains(pointer.gameX, pointer.gameY);

				switch (pointer.type) {
					case POINTER_MOVE[0]:
					case POINTER_MOVE[1]:
					case POINTER_MOVE[2]:
					case POINTER_MOVE[3]:
						// moved out of bounds: trigger the POINTER_LEAVE callbacks
						if (handlers.pointerId === pointer.pointerId && !eventInBounds) {
							if (
								triggerEvent(
									handlers,
									findActiveEvent(activeEventList, POINTER_LEAVE),
									pointer,
									null,
								)
							) {
								handled = true;
								break;
							}
						}
						// no pointer & moved inside of bounds: trigger the POINTER_ENTER callbacks
						else if (handlers.pointerId === null && eventInBounds) {
							if (
								triggerEvent(
									handlers,
									findActiveEvent(activeEventList, POINTER_ENTER),
									pointer,
									pointer.pointerId,
								)
							) {
								handled = true;
								break;
							}
						}

						// trigger the POINTER_MOVE callbacks
						if (
							eventInBounds &&
							triggerEvent(handlers, pointer.type, pointer, pointer.pointerId)
						) {
							handled = true;
							break;
						}
						break;

					case POINTER_UP[0]:
					case POINTER_UP[1]:
					case POINTER_UP[2]:
					case POINTER_UP[3]:
						// pointer defined & inside of bounds: trigger the POINTER_UP callback
						if (handlers.pointerId === pointer.pointerId && eventInBounds) {
							// trigger the corresponding callback
							if (triggerEvent(handlers, pointer.type, pointer, null)) {
								handled = true;
								break;
							}
						}
						break;

					case POINTER_CANCEL[0]:
					case POINTER_CANCEL[1]:
					case POINTER_CANCEL[2]:
					case POINTER_CANCEL[3]:
						// pointer defined: trigger the POINTER_CANCEL callback
						if (handlers.pointerId === pointer.pointerId) {
							// trigger the corresponding callback
							if (triggerEvent(handlers, pointer.type, pointer, null)) {
								handled = true;
								break;
							}
						}
						break;

					default:
						// event inside of bounds: trigger the POINTER_DOWN or WHEEL callback
						if (eventInBounds) {
							// trigger the corresponding callback
							if (
								triggerEvent(handlers, pointer.type, pointer, pointer.pointerId)
							) {
								handled = true;
								break;
							}
						}
						break;
				}
			}
			if (handled === true) {
				// stop iterating through this list of candidates
				break;
			}
		}
	}
	return handled;
}
propagate events to registered objects
@ignore