User prompt
smoothingFactor To 1
User prompt
smoothingFactor To 0.4
User prompt
Switch bypass to false
Code edit (1 edits merged)
Please save this source code
Code edit (6 edits merged)
Please save this source code
User prompt
switch bypassTracking to true
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (17 edits merged)
Please save this source code
User prompt
switch bypassTracking to false
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
User prompt
switch bypassTracking to true
Code edit (11 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'head')' in or related to this line: 'self.scales = {' Line Number: 238
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'head')' in or related to this line: 'self.scales = {' Line Number: 224
Code edit (1 edits merged)
Please save this source code
Code edit (8 edits merged)
Please save this source code
User prompt
switch bypassTracking to false
User prompt
switch bypassTracking to true
Code edit (1 edits merged)
Please save this source code
Code edit (18 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'parse')' in or related to this line: 'previousFacekit = JSON.parse(JSON.stringify(currentKit));' Line Number: 609
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'parse')' in or related to this line: 'previousFacekit = JSON.parse(JSON.stringify(currentKit));' Line Number: 600
Code edit (1 edits merged)
Please save this source code
/**** 
* Plugins
****/ 
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
var facekit = LK.import("@upit/facekit.v1");
/**** 
* Classes
****/ 
var DebugPoints = Container.expand(function () {
	var self = Container.call(this);
	// Create points for face tracking debugging
	self.points = {
		leftEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		rightEye: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		noseTip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		mouthCenter: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		upperLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		lowerLip: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		}),
		chin: self.attachAsset('debugFacePoints', {
			anchorX: 0.5,
			anchorY: 0.5
		})
	};
	// Update debug points to match face points
	self.update = function () {
		if (!facekit) {
			return;
		}
		if (facekit.leftEye) {
			self.points.leftEye.x = facekit.leftEye.x;
			self.points.leftEye.y = facekit.leftEye.y;
		}
		if (facekit.rightEye) {
			self.points.rightEye.x = facekit.rightEye.x;
			self.points.rightEye.y = facekit.rightEye.y;
		}
		if (facekit.noseTip) {
			self.points.noseTip.x = facekit.noseTip.x;
			self.points.noseTip.y = facekit.noseTip.y;
		}
		if (facekit.mouthCenter) {
			self.points.mouthCenter.x = facekit.mouthCenter.x;
			self.points.mouthCenter.y = facekit.mouthCenter.y;
		}
		if (facekit.upperLip) {
			self.points.upperLip.x = facekit.upperLip.x;
			self.points.upperLip.y = facekit.upperLip.y;
		}
		if (facekit.lowerLip) {
			self.points.lowerLip.x = facekit.lowerLip.x;
			self.points.lowerLip.y = facekit.lowerLip.y;
		}
		if (facekit.chin) {
			self.points.chin.x = facekit.chin.x;
			self.points.chin.y = facekit.chin.y;
		}
	};
	return self;
});
var TrollFace = Container.expand(function () {
	var self = Container.call(this);
	self.scales = {
		head: {
			x: 1,
			y: 1
		},
		eye: {
			x: 1,
			y: 1
		},
		lip: {
			x: 1,
			y: 1
		}
	};
	// Properties
	self.currentStyle = 1;
	self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
	self.elements = {};
	// Initialize cached position objects to reuse
	self.positionCache = {
		head: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		leftEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		rightEye: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		upperLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		},
		lowerLip: {
			x: 0,
			y: 0,
			scaleX: 1,
			scaleY: 1
		}
	};
	// Initialize the troll face elements
	self.initialize = function (style) {
		// Clear previous elements
		self.removeAllElements();
		// Create new elements for the selected style
		self.createFaceElements(style || self.currentStyle);
	};
	// Create face elements for a specific style
	self.createFaceElements = function (style) {
		// Create head
		self.elements.head = self.attachAsset('trollHead' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1,
			alpha: 1 // DEBUG
		});
		// Create eyes
		self.elements.leftEye = self.attachAsset('trollLeftEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		self.elements.rightEye = self.attachAsset('trollRightEye' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create upper lip
		self.elements.upperLip = self.attachAsset('trollUpperLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
		// Create lower lip
		self.elements.lowerLip = self.attachAsset('trollLowerLip' + style, {
			anchorX: 0.5,
			anchorY: 0.5,
			scale: 1
		});
	};
	// Remove all face elements
	self.removeAllElements = function () {
		if (self.elements.head) {
			self.removeChild(self.elements.head);
		}
		if (self.elements.leftEye) {
			self.removeChild(self.elements.leftEye);
		}
		if (self.elements.rightEye) {
			self.removeChild(self.elements.rightEye);
		}
		if (self.elements.upperLip) {
			self.removeChild(self.elements.upperLip);
		}
		if (self.elements.lowerLip) {
			self.removeChild(self.elements.lowerLip);
		}
	};
	// Change to the next troll face style
	self.nextStyle = function (forcedStyle) {
		self.currentStyle = forcedStyle || self.currentStyle % 3 + 1;
		self.initialize();
		self.currentOffsets = trollFaceOffsets[self.currentStyle - 1];
		// Calculate face scale once and reuse
		var baseScale = self.calculateFaceScale(facekit);
		// Calculate all scales upfront
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			eye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			lip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			}
		};
		return self.currentStyle;
	};
	// Helper function to clamp a value between min and max
	self.clampPosition = function (value, min, max) {
		return Math.min(Math.max(value, min), max);
	};
	// Helper function to ensure scale is an object
	self.ensureScaleIsObject = function (element) {
		if (_typeof(element.scale) !== 'object') {
			element.scale = {
				x: 1,
				y: 1
			};
		}
	};
	// Helper function to update a face element
	self.updateFaceElement = function (elementName, x, y, scaleX, scaleY, makeVisible) {
		var element = self.elements[elementName];
		if (!element) {
			return;
		}
		// Ensure scale is an object
		self.ensureScaleIsObject(element);
		// Apply position with clamping using scaled boundaries
		var elementOffset = self.currentOffsets[elementName];
		element.x = self.clampPosition(x, elementOffset.minX * currentEyeDistance, elementOffset.maxX * currentEyeDistance);
		element.y = self.clampPosition(y, elementOffset.minY * currentEyeDistance, elementOffset.maxY * currentEyeDistance);
		// Apply scale
		element.scale.x = scaleX;
		element.scale.y = scaleY;
		// Set visibility if needed
		if (makeVisible) {
			element.visible = true;
		}
	};
	// Helper function to update all face elements
	self.updateAllFaceElements = function (kit, makeVisible, useDefaultScales) {
		var elementNames = ['head', 'leftEye', 'rightEye', 'upperLip', 'lowerLip'];
		var positions = self.positionCache;
		// Calculate positions for all elements
		positions.head.x = 0;
		positions.head.y = 0;
		positions.head.scaleX = useDefaultScales ? 1 : self.scales.head.x;
		positions.head.scaleY = useDefaultScales ? 1 : self.scales.head.y;
		// Get the rotation angle for constraint calculations
		var rotationAngle = self.rotation;
		var cosAngle = Math.cos(-rotationAngle); // Negative to counter-rotate
		var sinAngle = Math.sin(-rotationAngle);
		// For other elements, calculate based on kit positions
		for (var i = 1; i < elementNames.length; i++) {
			var name = elementNames[i];
			var kitElement = kit[name];
			if (kitElement) {
				var scaleX, scaleY;
				// Determine which scale to use based on element type and useDefaultScales flag
				if (useDefaultScales) {
					scaleX = 1 * self.currentOffsets[name].sx;
					scaleY = 1 * self.currentOffsets[name].sy;
				} else {
					if (name === 'leftEye' || name === 'rightEye') {
						scaleX = self.scales.eye.x;
						scaleY = self.scales.eye.y;
					} else {
						scaleX = self.scales.lip.x;
						scaleY = self.scales.lip.y;
					}
				}
				// Calculate position using relative offsets scaled by eye distance
				var rawX = kitElement.x - self.x + self.currentOffsets[name].x * currentEyeDistance;
				var rawY = kitElement.y - self.y + self.currentOffsets[name].y * currentEyeDistance;
				// Apply rotation constraints to maintain relative positions
				// This counter-rotates the positions to keep elements in proper alignment
				positions[name].x = rawX * cosAngle - rawY * sinAngle;
				positions[name].y = rawX * sinAngle + rawY * cosAngle;
				positions[name].scaleX = scaleX;
				positions[name].scaleY = scaleY;
			}
		}
		// Update each element with calculated positions
		for (var j = 0; j < elementNames.length; j++) {
			var elemName = elementNames[j];
			if (self.elements[elemName] && positions[elemName]) {
				var pos = positions[elemName];
				self.updateFaceElement(elemName, pos.x, pos.y, pos.scaleX, pos.scaleY, makeVisible);
			}
		}
		// Handle mouth open adjustment
		if (kit.mouthOpen && self.elements.lowerLip) {
			//self.elements.lowerLip.scale.y = self.scales.lip.y * 1.5;
		}
	};
	// Update face elements to match real face
	self.updateFacePosition = function () {
		if (!facekit) {
			return;
		}
		// Get kit based on tracking state
		var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit;
		// If re-centering, use fakeCamera data for positioning without changing bypassTracking
		if (self.isRecentering) {
			kit = fakeCamera;
		}
		// Update global eye distance once per frame
		currentEyeDistance = self.getEyeDistance(kit);
		var baseScale = self.calculateFaceScale(kit);
		// Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation
		var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit);
		// If re-centering, reset rotation to 0
		if (self.isRecentering) {
			rotation = 0;
		}
		// Update scales
		self.scales = {
			head: {
				x: baseScale * self.currentOffsets.head.sx,
				y: baseScale * self.currentOffsets.head.sy
			},
			eye: {
				x: baseScale * self.currentOffsets.leftEye.sx,
				y: baseScale * self.currentOffsets.leftEye.sy
			},
			lip: {
				x: baseScale * self.currentOffsets.upperLip.sx,
				y: baseScale * self.currentOffsets.upperLip.sy
			}
		};
		if (bypassTracking) {
			self.x = 2048 / 2;
			self.y = 2732 / 2;
			self.rotation = 0; // Reset rotation in bypass mode
			// Use the global fakeCamera with dynamic scales
			self.updateAllFaceElements(fakeCamera, true, false);
			return;
		}
		// Apply rotation to the entire troll face container
		self.rotation = rotation;
		// Update all elements with kit
		self.updateAllFaceElements(kit, true, false);
	};
	// Calculate scale based on eye distance
	self.calculateFaceScale = function (kit) {
		return currentEyeDistance * 0.005;
	};
	// Calculate face rotation angle based on eye positions
	self.calculateFaceRotation = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			// Get eye positions
			var leftEye = kit.leftEye;
			var rightEye = kit.rightEye;
			// Calculate angle - simpler approach
			// This gives us the angle of the line connecting the eyes
			var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x);
			// More efficient angle wrapping using modulo
			var angleDiff = angle - currentRotation;
			// Normalize the difference to [-PI, PI] range
			angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI;
			// Apply smoothing to the rotation
			currentRotation = currentRotation + angleDiff * 0.2;
			return currentRotation + Math.PI;
		}
		return 0; // Default rotation (no rotation)
	};
	// Get eye distance from kit
	self.getEyeDistance = function (kit) {
		if (kit && kit.leftEye && kit.rightEye) {
			var dx = kit.leftEye.x - kit.rightEye.x;
			var dy = kit.leftEye.y - kit.rightEye.y;
			return Math.sqrt(dx * dx + dy * dy);
		}
		return 200; // Default eye distance
	};
	// Add a property to track if we're in re-centering mode
	self.isRecentering = false;
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0xFFFFFF
});
/**** 
* Game Code
****/ 
/**** 
* Global Variables
****/ 
var debugMode = false; // DEBUG MODE DEBUG MODE  DEBUG MODE 
var bypassTracking = false; // Global variable to bypass face tracking
var currentEyeDistance = 200; // Global variable to store current eye distance
var currentRotation = 0; // Current smoothed rotation value
// FacekitManager: handles face tracking, smoothing, and fallback
var FacekitManager = function FacekitManager() {
	var self = new Container();
	// Public properties
	self.currentFacekit = null; // Current smoothed face data
	self.isTracking = false; // Current tracking state
	self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance)
	self.smoothingFactor = 0.2; // Default smoothing factor
	self.lastNosePosition = null; // Last nose position for tracking detection
	self.trackingStoppedCounter = 100; // Counter for tracking detection
	self.trackingStoppedDelay = 100; // Delay threshold for tracking detection
	// Default positions - defined inline
	var defaultFacekit = {
		leftEye: {
			x: 1380,
			y: 958
		},
		rightEye: {
			x: 673,
			y: 970
		},
		upperLip: {
			x: 1027,
			y: 1610
		},
		lowerLip: {
			x: 1030,
			y: 1613
		},
		noseTip: {
			x: 1024,
			y: 1366
		},
		mouthOpen: false
	};
	// Initialize manager
	self.initialize = function (fakeFacekit) {
		// Use provided fallback or default
		self.fallbackFacekit = fakeFacekit || defaultFacekit;
		// Initialize current data as reference to fallback
		self.currentFacekit = self.fallbackFacekit;
		// Create a reusable object for smoothing calculations
		self.smoothFacekit = {
			leftEye: {
				x: 0,
				y: 0
			},
			rightEye: {
				x: 0,
				y: 0
			},
			upperLip: {
				x: 0,
				y: 0
			},
			lowerLip: {
				x: 0,
				y: 0
			},
			noseTip: {
				x: 0,
				y: 0
			},
			mouthOpen: false
		};
		return self;
	};
	// Process new tracking data and update tracking status
	self.updateTrackingStatus = function (rawFacekit) {
		// Check if there's valid face data
		var hasFaceData = !!(rawFacekit && rawFacekit.noseTip);
		if (!hasFaceData) {
			self.isTracking = false;
			return false;
		}
		// Check if nose position has changed using existing tracking mechanism
		if (self.lastNosePosition === rawFacekit.noseTip.x) {
			self.trackingStoppedCounter--;
			if (self.trackingStoppedCounter <= 0) {
				self.isTracking = false;
				self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
			}
		} else {
			self.isTracking = true;
			self.lastNosePosition = rawFacekit.noseTip.x;
			self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
		}
		// If tracking, process the face data
		if (self.isTracking) {
			// If tracking just started, initialize smooth values
			if (self.currentFacekit === self.fallbackFacekit) {
				self._initSmoothValues(rawFacekit);
			}
			// Apply smoothing to each facial feature
			self._smoothValues(rawFacekit);
			// Set currentFacekit to the smoothed values
			self.currentFacekit = self.smoothFacekit;
		} else {
			// Use fallback when not tracking
			self.currentFacekit = self.fallbackFacekit;
		}
		// Return tracking state
		return self.isTracking;
	};
	// Initialize smooth values with raw data
	self._initSmoothValues = function (rawFacekit) {
		// Directly set initial values from raw data
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				self.smoothFacekit[key].x = rawFacekit[key].x;
				self.smoothFacekit[key].y = rawFacekit[key].y;
			}
		}
		// Initialize rotation directly
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
		}
	};
	// Apply smoothing to all values
	self._smoothValues = function (rawFacekit) {
		// Smooth positions (reuse existing objects)
		for (var key in rawFacekit) {
			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
				// Apply position smoothing directly
				self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * self.smoothingFactor;
				self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * self.smoothingFactor;
			}
		}
		// Calculate and smooth rotation
		if (rawFacekit.leftEye && rawFacekit.rightEye) {
			var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
			// Improved angle normalization to prevent full rotations - no while loops
			var angleDiff = newAngle - self.currentRotation;
			// Efficiently handle -PI/PI boundary crossing (replaces the while loops)
			if (angleDiff > Math.PI) {
				angleDiff -= 2 * Math.PI;
			} else if (angleDiff < -Math.PI) {
				angleDiff += 2 * Math.PI;
			}
			// Apply safeguard against large changes
			var maxRotationPerFrame = Math.PI / 10; // Limit rotation change
			// Clamp the rotation change to prevent sudden large rotations
			if (angleDiff > maxRotationPerFrame) {
				angleDiff = maxRotationPerFrame;
			}
			if (angleDiff < -maxRotationPerFrame) {
				angleDiff = -maxRotationPerFrame;
			}
			// Apply smoothing
			self.currentRotation += angleDiff * self.smoothingFactor;
			// Normalize the result angle to ensure it stays in -PI to PI range
			// Using efficient modulo approach rather than while loops
			self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
		}
		// Copy mouth state
		self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen;
	};
	return self;
};
var trollFaceOffsets = [{
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0.75,
		// 150/200 = 0.75
		y: 1.0,
		// 200/200 = 1.0
		sx: 0.6,
		sy: 0.6,
		minX: -1.0,
		// -200/200 = -1.0
		maxX: 1.0,
		// 200/200 = 1.0
		minY: -1.0,
		maxY: 1.0
	},
	rightEye: {
		x: 0,
		// 0/200 = 0
		y: 1.0,
		// 200/200 = 1.0
		sx: 0.6,
		sy: 0.6,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	},
	upperLip: {
		x: 0,
		// 0/200 = 0
		y: 0.15,
		// 30/200 = 0.15
		sx: 1,
		sy: 1,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	},
	lowerLip: {
		x: 0,
		// 0/200 = 0
		y: 0.65,
		// 130/200 = 0.65
		sx: 1,
		sy: 1,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: 0,
		y: 0.5,
		sx: 0.6,
		sy: 0.6,
		minX: 0.2,
		maxX: 0.7,
		minY: 0,
		maxY: 0.6
	},
	rightEye: {
		x: 0.1,
		y: 0.3,
		sx: 0.6,
		sy: 0.6,
		minX: -0.7,
		maxX: 0.1,
		minY: 0,
		maxY: 0.4
	},
	upperLip: {
		x: -0.2,
		y: 0.1,
		sx: 0.5,
		sy: 0.5,
		minX: -0.3,
		maxX: 0,
		minY: -1,
		maxY: 1
	},
	lowerLip: {
		x: -0.10,
		y: 0.12,
		sx: 0.2,
		sy: 0.2,
		minX: -0.2,
		maxX: 0,
		minY: 0,
		maxY: 0.8
	}
}, {
	head: {
		x: 0,
		y: 0,
		sx: 1,
		sy: 1
	},
	leftEye: {
		x: -0.275,
		// -55/200 = -0.275
		y: -1.0,
		// -200/200 = -1.0
		sx: 1,
		sy: 1,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	},
	rightEye: {
		x: -0.775,
		// -155/200 = -0.775
		y: -1.0,
		// -200/200 = -1.0
		sx: 1,
		sy: 1,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	},
	upperLip: {
		x: 0,
		// 0/200 = 0
		y: 0.16,
		// 32/200 = 0.16
		sx: 1,
		sy: 1.5,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	},
	lowerLip: {
		x: 0,
		// 0/200 = 0
		y: 0.26,
		// 52/200 = 0.26
		sx: 1,
		sy: 1.5,
		minX: -1.0,
		maxX: 1.0,
		minY: -1.0,
		maxY: 1.0
	}
}];
// Define fakeCamera globally with fixed positions for testing
var fakeCamera = {
	leftEye: {
		x: 1380,
		y: 958
	},
	rightEye: {
		x: 673,
		y: 970
	},
	upperLip: {
		x: 1027,
		y: 1610
	},
	lowerLip: {
		x: 1030,
		y: 1613
	},
	mouthOpen: false
}; // Global object for bypass tracking mode
// Initialize FacekitManager
var facekitMgr = new FacekitManager();
facekitMgr.initialize(fakeCamera);
var background;
var instructionText;
var styleText;
var trollFace;
var debugPoints;
var backgroundContainer;
var middlegroundContainer;
var foregroundContainer;
var isTrackingFace = false; // Global flag to track face detection state
var targetPosition;
var lastNosePosition = null; // Global variable to store last facekit.noseTip.x
var trackingStoppedDelay = 100;
var trackingStoppedCounter = trackingStoppedDelay;
function _typeof(o) {
	"@babel/helpers - typeof";
	return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
		return typeof o;
	} : function (o) {
		return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
	}, _typeof(o);
}
/**** 
* Game Functions
****/ 
// Handle tap anywhere on the screen to change face
game.down = function (x, y, obj) {
	// Switch to the next troll face style
	var newStyle = trollFace.nextStyle();
	// Save the current style to storage
	storage.lastTrollStyle = newStyle;
	// Update the style text
	styleText.setText('Style: ' + newStyle + '/3');
	// Play switch sound
	LK.getSound('switchTroll').play();
};
// Update function called every frame
game.update = function () {
	if (bypassTracking) {
		// When bypassing tracking, position the troll face at the center and update its elements
		trollFace.updateFacePosition();
		return;
	}
	if (!facekit || !facekit.noseTip) {
		return;
	}
	// Update tracking status and get smoothed face data
	var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit);
	// Update tracking state UI if changed
	if (isCurrentlyTracking !== isTrackingFace) {
		isTrackingFace = isCurrentlyTracking;
		instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found");
	}
	// Update troll face position to match real face
	if (isTrackingFace) {
		// Reset isRecentering flag when face tracking is detected
		trollFace.isRecentering = false;
		// Use the smoothed nose tip position
		trollFace.x = facekitMgr.currentFacekit.noseTip.x;
		trollFace.y = facekitMgr.currentFacekit.noseTip.y;
		// Use the original updateFacePosition with smoothed data
		trollFace.updateFacePosition();
		trollFace.isCentered = false;
		trollFace.isCentering = false;
	} else {
		// If face is not detected, return the face to the center
		if (!trollFace.isCentered) {
			if (trollFace.isCentering) {
				// Don't exit the update function, just skip starting a new tween
				// This allows other updates to continue	
				// Continue updating face elements during centering
				trollFace.updateFacePosition();
			} else {
				trollFace.isCentering = true;
				// Set isRecentering flag to use fakeCamera data for positioning
				trollFace.isRecentering = true;
				LK.effects.flashScreen(0xFFFFFF, 1000); // Flash screen
				tween(trollFace, {
					x: 2048 / 2,
					y: 2732 / 2
				}, {
					duration: 500,
					easing: tween.easeInOut,
					onFinish: function onFinish() {
						trollFace.isCentered = true;
						trollFace.isCentering = false;
						// Keep isRecentering true to maintain centered elements
						trollFace.updateFacePosition();
					}
				});
			}
		}
		// Update face tracking state
		if (isTrackingFace) {
			isTrackingFace = false;
			instructionText.setText("No Face found");
		}
	}
	// If in debug mode, display the face elements positions
	if (debugMode) {
		// Clear existing debug points
		if (debugPoints) {
			for (var i = 0; i < debugPoints.children.length; i++) {
				debugPoints.children[i].alpha = 0;
			}
		}
		// Draw debug points for each face element
		if (facekit) {
			var pointIndex = 0;
			// Draw a point for each facial feature
			for (var key in facekit) {
				if (facekit[key] && typeof facekit[key].x !== 'undefined') {
					var point = debugPoints.children[pointIndex++];
					if (point) {
						point.x = facekit[key].x;
						point.y = facekit[key].y;
						point.alpha = 1;
					}
				}
			}
			// Display rotation in degrees
			instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "°");
		}
	}
};
function initializeGame() {
	// Initialize game
	// Create containers for layering
	backgroundContainer = new Container();
	middlegroundContainer = new Container();
	foregroundContainer = new Container();
	// Add containers to game
	game.addChild(backgroundContainer);
	game.addChild(middlegroundContainer);
	game.addChild(foregroundContainer);
	// Global target position for the troll face
	targetPosition = {
		x: 2048 / 2,
		y: 2732 / 2
	};
	// Setup background
	background = LK.getAsset('whiteBackground', {
		anchorX: 0.5,
		anchorY: 0.5,
		x: 2048 / 2,
		y: 2732 / 2,
		visible: false
	});
	backgroundContainer.addChild(background);
	// Setup UI text
	instructionText = new Text2('Tap anywhere to change troll face', {
		size: 50,
		fill: 0xFF0000
	});
	instructionText.anchor.set(0.5, 0);
	LK.gui.top.addChild(instructionText);
	instructionText.y = 1700;
	// Load the last used style from storage
	var lastStyle = storage.lastTrollStyle || 1;
	// Create the troll face
	trollFace = new TrollFace();
	trollFace.currentStyle = lastStyle;
	trollFace.initialize();
	trollFace.nextStyle(lastStyle);
	middlegroundContainer.addChild(trollFace);
	// Set the troll face position to the center of the screen
	trollFace.x = 2048 / 2;
	trollFace.y = 2732 / 2;
	// Initialize tracking state
	isTrackingFace = false;
	// Add style text
	styleText = new Text2('Style: ' + lastStyle + '/3', {
		size: 50,
		fill: 0xFF0000
	});
	styleText.anchor.set(0.5, 0);
	LK.gui.top.addChild(styleText);
	styleText.y = 120;
	// Debug mode (turn on for development, off for production)
	debugPoints = null;
	if (debugMode) {
		debugPoints = new DebugPoints();
		foregroundContainer.addChild(debugPoints);
		// Log facekit to console every second
		LK.setInterval(function () {
			console.log(facekit);
			if (facekit.lowerLip) {
				var elementOffset = trollFace.currentOffsets.lowerLip;
				var eyeDistance = trollFace.getEyeDistance(facekit);
				console.log("lowerLip y:", facekit.lowerLip.y, "minY:", elementOffset.minY * eyeDistance, "maxY:", elementOffset.maxY * eyeDistance);
			}
			// Display rotation angle in degrees for debugging
			var rotationDegrees = Math.round(trollFace.rotation * (180 / Math.PI));
			instructionText.setText("le:".concat(Math.round(facekit.leftEye.x), ",").concat(Math.round(facekit.leftEye.y), " / ") + "re:".concat(Math.round(facekit.rightEye.x), ",").concat(Math.round(facekit.rightEye.y), " / ") + "ul:".concat(Math.round(facekit.upperLip.x), ",").concat(Math.round(facekit.upperLip.y), " / ") + "ll:".concat(Math.round(facekit.lowerLip.x), ",").concat(Math.round(facekit.lowerLip.y), " / ") + "rot:".concat(rotationDegrees, "°"));
		}, 1000);
	}
}
// Initialize the game
initializeGame(); ===================================================================
--- original.js
+++ change.js
@@ -305,17 +305,23 @@
 	self.updateFacePosition = function () {
 		if (!facekit) {
 			return;
 		}
-		// Recalculate scales based on the current kit
-		var kit = bypassTracking ? fakeCamera : facekit;
+		// Get kit based on tracking state
+		var kit = bypassTracking ? fakeCamera : facekitMgr.isTracking ? facekitMgr.currentFacekit : facekit;
+		// If re-centering, use fakeCamera data for positioning without changing bypassTracking
+		if (self.isRecentering) {
+			kit = fakeCamera;
+		}
 		// Update global eye distance once per frame
 		currentEyeDistance = self.getEyeDistance(kit);
 		var baseScale = self.calculateFaceScale(kit);
-		// Calculate face rotation
-		var rotation = self.calculateFaceRotation(kit);
-		// Add PI (180 degrees) to flip the face right-side up, remove negative sign to fix direction
-		rotation = rotation + Math.PI;
+		// Calculate face rotation - use FacekitManager if tracking, otherwise use local calculation
+		var rotation = facekitMgr.isTracking ? facekitMgr.currentRotation + Math.PI : self.calculateFaceRotation(kit);
+		// If re-centering, reset rotation to 0
+		if (self.isRecentering) {
+			rotation = 0;
+		}
 		// Update scales
 		self.scales = {
 			head: {
 				x: baseScale * self.currentOffsets.head.sx,
@@ -339,10 +345,10 @@
 			return;
 		}
 		// Apply rotation to the entire troll face container
 		self.rotation = rotation;
-		// Update all elements with facekit
-		self.updateAllFaceElements(facekit, true, false);
+		// Update all elements with kit
+		self.updateAllFaceElements(kit, true, false);
 	};
 	// Calculate scale based on eye distance
 	self.calculateFaceScale = function (kit) {
 		return currentEyeDistance * 0.005;
@@ -355,18 +361,15 @@
 			var rightEye = kit.rightEye;
 			// Calculate angle - simpler approach
 			// This gives us the angle of the line connecting the eyes
 			var angle = Math.atan2(rightEye.y - leftEye.y, rightEye.x - leftEye.x);
-			// Handle angle wrapping to prevent 360° jumps
-			while (angle - currentRotation > Math.PI) {
-				angle -= 2 * Math.PI;
-			}
-			while (angle - currentRotation < -Math.PI) {
-				angle += 2 * Math.PI;
-			}
+			// More efficient angle wrapping using modulo
+			var angleDiff = angle - currentRotation;
+			// Normalize the difference to [-PI, PI] range
+			angleDiff = (angleDiff + Math.PI) % (2 * Math.PI) - Math.PI;
 			// Apply smoothing to the rotation
-			currentRotation = currentRotation + (angle - currentRotation) * 0.2;
-			return currentRotation;
+			currentRotation = currentRotation + angleDiff * 0.2;
+			return currentRotation + Math.PI;
 		}
 		return 0; // Default rotation (no rotation)
 	};
 	// Get eye distance from kit
@@ -377,8 +380,10 @@
 			return Math.sqrt(dx * dx + dy * dy);
 		}
 		return 200; // Default eye distance
 	};
+	// Add a property to track if we're in re-centering mode
+	self.isRecentering = false;
 	return self;
 });
 
 /**** 
@@ -397,8 +402,167 @@
 var debugMode = false; // DEBUG MODE DEBUG MODE  DEBUG MODE 
 var bypassTracking = false; // Global variable to bypass face tracking
 var currentEyeDistance = 200; // Global variable to store current eye distance
 var currentRotation = 0; // Current smoothed rotation value
+// FacekitManager: handles face tracking, smoothing, and fallback
+var FacekitManager = function FacekitManager() {
+	var self = new Container();
+	// Public properties
+	self.currentFacekit = null; // Current smoothed face data
+	self.isTracking = false; // Current tracking state
+	self.currentRotation = 0; // Current smoothed rotation (accessible directly for performance)
+	self.smoothingFactor = 0.2; // Default smoothing factor
+	self.lastNosePosition = null; // Last nose position for tracking detection
+	self.trackingStoppedCounter = 100; // Counter for tracking detection
+	self.trackingStoppedDelay = 100; // Delay threshold for tracking detection
+	// Default positions - defined inline
+	var defaultFacekit = {
+		leftEye: {
+			x: 1380,
+			y: 958
+		},
+		rightEye: {
+			x: 673,
+			y: 970
+		},
+		upperLip: {
+			x: 1027,
+			y: 1610
+		},
+		lowerLip: {
+			x: 1030,
+			y: 1613
+		},
+		noseTip: {
+			x: 1024,
+			y: 1366
+		},
+		mouthOpen: false
+	};
+	// Initialize manager
+	self.initialize = function (fakeFacekit) {
+		// Use provided fallback or default
+		self.fallbackFacekit = fakeFacekit || defaultFacekit;
+		// Initialize current data as reference to fallback
+		self.currentFacekit = self.fallbackFacekit;
+		// Create a reusable object for smoothing calculations
+		self.smoothFacekit = {
+			leftEye: {
+				x: 0,
+				y: 0
+			},
+			rightEye: {
+				x: 0,
+				y: 0
+			},
+			upperLip: {
+				x: 0,
+				y: 0
+			},
+			lowerLip: {
+				x: 0,
+				y: 0
+			},
+			noseTip: {
+				x: 0,
+				y: 0
+			},
+			mouthOpen: false
+		};
+		return self;
+	};
+	// Process new tracking data and update tracking status
+	self.updateTrackingStatus = function (rawFacekit) {
+		// Check if there's valid face data
+		var hasFaceData = !!(rawFacekit && rawFacekit.noseTip);
+		if (!hasFaceData) {
+			self.isTracking = false;
+			return false;
+		}
+		// Check if nose position has changed using existing tracking mechanism
+		if (self.lastNosePosition === rawFacekit.noseTip.x) {
+			self.trackingStoppedCounter--;
+			if (self.trackingStoppedCounter <= 0) {
+				self.isTracking = false;
+				self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
+			}
+		} else {
+			self.isTracking = true;
+			self.lastNosePosition = rawFacekit.noseTip.x;
+			self.trackingStoppedCounter = self.trackingStoppedDelay; // Reset delay
+		}
+		// If tracking, process the face data
+		if (self.isTracking) {
+			// If tracking just started, initialize smooth values
+			if (self.currentFacekit === self.fallbackFacekit) {
+				self._initSmoothValues(rawFacekit);
+			}
+			// Apply smoothing to each facial feature
+			self._smoothValues(rawFacekit);
+			// Set currentFacekit to the smoothed values
+			self.currentFacekit = self.smoothFacekit;
+		} else {
+			// Use fallback when not tracking
+			self.currentFacekit = self.fallbackFacekit;
+		}
+		// Return tracking state
+		return self.isTracking;
+	};
+	// Initialize smooth values with raw data
+	self._initSmoothValues = function (rawFacekit) {
+		// Directly set initial values from raw data
+		for (var key in rawFacekit) {
+			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
+				self.smoothFacekit[key].x = rawFacekit[key].x;
+				self.smoothFacekit[key].y = rawFacekit[key].y;
+			}
+		}
+		// Initialize rotation directly
+		if (rawFacekit.leftEye && rawFacekit.rightEye) {
+			self.currentRotation = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
+		}
+	};
+	// Apply smoothing to all values
+	self._smoothValues = function (rawFacekit) {
+		// Smooth positions (reuse existing objects)
+		for (var key in rawFacekit) {
+			if (rawFacekit[key] && self.smoothFacekit[key] && typeof rawFacekit[key].x !== 'undefined') {
+				// Apply position smoothing directly
+				self.smoothFacekit[key].x += (rawFacekit[key].x - self.smoothFacekit[key].x) * self.smoothingFactor;
+				self.smoothFacekit[key].y += (rawFacekit[key].y - self.smoothFacekit[key].y) * self.smoothingFactor;
+			}
+		}
+		// Calculate and smooth rotation
+		if (rawFacekit.leftEye && rawFacekit.rightEye) {
+			var newAngle = Math.atan2(rawFacekit.rightEye.y - rawFacekit.leftEye.y, rawFacekit.rightEye.x - rawFacekit.leftEye.x);
+			// Improved angle normalization to prevent full rotations - no while loops
+			var angleDiff = newAngle - self.currentRotation;
+			// Efficiently handle -PI/PI boundary crossing (replaces the while loops)
+			if (angleDiff > Math.PI) {
+				angleDiff -= 2 * Math.PI;
+			} else if (angleDiff < -Math.PI) {
+				angleDiff += 2 * Math.PI;
+			}
+			// Apply safeguard against large changes
+			var maxRotationPerFrame = Math.PI / 10; // Limit rotation change
+			// Clamp the rotation change to prevent sudden large rotations
+			if (angleDiff > maxRotationPerFrame) {
+				angleDiff = maxRotationPerFrame;
+			}
+			if (angleDiff < -maxRotationPerFrame) {
+				angleDiff = -maxRotationPerFrame;
+			}
+			// Apply smoothing
+			self.currentRotation += angleDiff * self.smoothingFactor;
+			// Normalize the result angle to ensure it stays in -PI to PI range
+			// Using efficient modulo approach rather than while loops
+			self.currentRotation = (self.currentRotation + 3 * Math.PI) % (2 * Math.PI) - Math.PI;
+		}
+		// Copy mouth state
+		self.smoothFacekit.mouthOpen = rawFacekit.mouthOpen;
+	};
+	return self;
+};
 var trollFaceOffsets = [{
 	head: {
 		x: 0,
 		y: 0,
@@ -577,8 +741,11 @@
 		y: 1613
 	},
 	mouthOpen: false
 }; // Global object for bypass tracking mode
+// Initialize FacekitManager
+var facekitMgr = new FacekitManager();
+facekitMgr.initialize(fakeCamera);
 var background;
 var instructionText;
 var styleText;
 var trollFace;
@@ -622,55 +789,26 @@
 	}
 	if (!facekit || !facekit.noseTip) {
 		return;
 	}
-	// Check if lastNosePosition is the same after trackingStoppedDelay ticks
-	if (lastNosePosition === facekit.noseTip.x) {
-		trackingStoppedCounter--;
-		if (trackingStoppedCounter <= 0) {
-			isTrackingFace = false;
-			instructionText.setText("Stopped tracking");
-			trackingStoppedCounter = trackingStoppedDelay; // Reset delay
-		}
-	} else {
-		isTrackingFace = true;
-		lastNosePosition = facekit.noseTip.x;
-		instructionText.setText("Tracking...");
-		trackingStoppedCounter = trackingStoppedDelay; // Reset delay
+	// Update tracking status and get smoothed face data
+	var isCurrentlyTracking = facekitMgr.updateTrackingStatus(facekit);
+	// Update tracking state UI if changed
+	if (isCurrentlyTracking !== isTrackingFace) {
+		isTrackingFace = isCurrentlyTracking;
+		instructionText.setText(isCurrentlyTracking ? "Tracking..." : "No Face found");
 	}
 	// Update troll face position to match real face
 	if (isTrackingFace) {
-		// Use the nose tip position for the face with easing
-		targetPosition.x = facekit.noseTip.x;
-		targetPosition.y = facekit.noseTip.y;
-		trollFace.x = targetPosition.x;
-		trollFace.y = targetPosition.y;
-		trollFace.isTweening = true; // TEMP DEBUG
-		// Apply tweening for smooth movement and wait for it to finish before starting the next
-		// if (!trollFace.isTweening) {
-		// 	trollFace.isTweening = true;
-		// 	tween(trollFace, {
-		// 		x: targetPosition.x,
-		// 		y: targetPosition.y
-		// 	}, {
-		// 		duration: 300,
-		// 		easing: tween.easeInOut,
-		// 		onFinish: function onFinish() {
-		// 			trollFace.isTweening = false;
-		// 		}
-		// 	});
-		// }
-		// Update face elements to match real face features
+		// Reset isRecentering flag when face tracking is detected
+		trollFace.isRecentering = false;
+		// Use the smoothed nose tip position
+		trollFace.x = facekitMgr.currentFacekit.noseTip.x;
+		trollFace.y = facekitMgr.currentFacekit.noseTip.y;
+		// Use the original updateFacePosition with smoothed data
 		trollFace.updateFacePosition();
 		trollFace.isCentered = false;
 		trollFace.isCentering = false;
-		// Update face tracking state
-		if (!isTrackingFace) {
-			isTrackingFace = true;
-			instructionText.setText("Tracking..." + new Date());
-			console.log("facekit", facekit);
-		}
-		//console.log("noseTip x=", facekit.noseTip.x);
 	} else {
 		// If face is not detected, return the face to the center
 		if (!trollFace.isCentered) {
 			if (trollFace.isCentering) {
@@ -679,8 +817,10 @@
 				// Continue updating face elements during centering
 				trollFace.updateFacePosition();
 			} else {
 				trollFace.isCentering = true;
+				// Set isRecentering flag to use fakeCamera data for positioning
+				trollFace.isRecentering = true;
 				LK.effects.flashScreen(0xFFFFFF, 1000); // Flash screen
 				tween(trollFace, {
 					x: 2048 / 2,
 					y: 2732 / 2
@@ -689,8 +829,9 @@
 					easing: tween.easeInOut,
 					onFinish: function onFinish() {
 						trollFace.isCentered = true;
 						trollFace.isCentering = false;
+						// Keep isRecentering true to maintain centered elements
 						trollFace.updateFacePosition();
 					}
 				});
 			}
@@ -700,8 +841,34 @@
 			isTrackingFace = false;
 			instructionText.setText("No Face found");
 		}
 	}
+	// If in debug mode, display the face elements positions
+	if (debugMode) {
+		// Clear existing debug points
+		if (debugPoints) {
+			for (var i = 0; i < debugPoints.children.length; i++) {
+				debugPoints.children[i].alpha = 0;
+			}
+		}
+		// Draw debug points for each face element
+		if (facekit) {
+			var pointIndex = 0;
+			// Draw a point for each facial feature
+			for (var key in facekit) {
+				if (facekit[key] && typeof facekit[key].x !== 'undefined') {
+					var point = debugPoints.children[pointIndex++];
+					if (point) {
+						point.x = facekit[key].x;
+						point.y = facekit[key].y;
+						point.alpha = 1;
+					}
+				}
+			}
+			// Display rotation in degrees
+			instructionText.setText("Rotation: " + Math.round(trollFace.rotation * 180 / Math.PI) + "°");
+		}
+	}
 };
 function initializeGame() {
 	// Initialize game
 	// Create containers for layering