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;
		// 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
				positions[name].x = kitElement.x - self.x + self.currentOffsets[name].x * currentEyeDistance;
				positions[name].y = kitElement.y - self.y + self.currentOffsets[name].y * currentEyeDistance;
				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;
		}
		// Recalculate scales based on the current kit
		var kit = bypassTracking ? fakeCamera : facekit;
		// 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;
		// 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 facekit
		self.updateAllFaceElements(facekit, 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);
			// 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;
			}
			// Apply smoothing to the rotation
			currentRotation = currentRotation + (angle - currentRotation) * 0.2;
			return currentRotation;
		}
		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
	};
	return self;
});
/**** 
* Initialize Game
****/ 
var game = new LK.Game({
	backgroundColor: 0xFFFFFF
});
/**** 
* Game Code
****/ 
/**** 
* Global Variables
****/ 
var debugMode = true; // DEBUG MODE DEBUG MODE  DEBUG MODE 
var bypassTracking = true; // Global variable to bypass face tracking
var currentEyeDistance = 200; // Global variable to store current eye distance
var currentRotation = 0; // Current smoothed rotation value
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
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;
	}
	// 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 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
		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) {
				// 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;
				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;
						trollFace.updateFacePosition();
					}
				});
			}
		}
		// Update face tracking state
		if (isTrackingFace) {
			isTrackingFace = false;
			instructionText.setText("No Face found");
		}
	}
};
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
@@ -292,82 +292,22 @@
 		if (kit.mouthOpen && self.elements.lowerLip) {
 			//self.elements.lowerLip.scale.y = self.scales.lip.y * 1.5;
 		}
 	};
-	// Helper function to check if position has changed significantly
-	self.hasPositionChangedSignificantly = function (oldPos, newPos) {
-		if (!oldPos || !newPos) {
-			return true;
-		}
-		var dx = Math.abs(oldPos.x - newPos.x);
-		var dy = Math.abs(oldPos.y - newPos.y);
-		return dx > movementThreshold || dy > movementThreshold;
-	};
 	// Update face elements to match real face
 	self.updateFacePosition = function () {
 		if (!facekit) {
 			return;
 		}
 		// Recalculate scales based on the current kit
 		var kit = bypassTracking ? fakeCamera : facekit;
-		// In bypass mode, always update
-		if (bypassTracking) {
-			// Update global eye distance once per frame
-			currentEyeDistance = self.getEyeDistance(kit);
-			var baseScale = self.calculateFaceScale(kit);
-			// 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
-				}
-			};
-			self.x = 2048 / 2;
-			self.y = 2732 / 2;
-			// Use the global fakeCamera with dynamic scales
-			self.updateAllFaceElements(fakeCamera, true, false);
-			return;
-		}
-		// Quick check for significant movement - only check eyes as they're most important
-		var shouldUpdate = false;
-		if (kit.leftEye && kit.rightEye) {
-			if (self.hasPositionChangedSignificantly(lastPositions.leftEye, kit.leftEye) || self.hasPositionChangedSignificantly(lastPositions.rightEye, kit.rightEye)) {
-				shouldUpdate = true;
-				// Update stored positions
-				lastPositions.leftEye.x = kit.leftEye.x;
-				lastPositions.leftEye.y = kit.leftEye.y;
-				lastPositions.rightEye.x = kit.rightEye.x;
-				lastPositions.rightEye.y = kit.rightEye.y;
-				// Only update other positions if we're already updating
-				if (kit.upperLip) {
-					lastPositions.upperLip.x = kit.upperLip.x;
-					lastPositions.upperLip.y = kit.upperLip.y;
-				}
-				if (kit.lowerLip) {
-					lastPositions.lowerLip.x = kit.lowerLip.x;
-					lastPositions.lowerLip.y = kit.lowerLip.y;
-				}
-				if (kit.noseTip) {
-					lastPositions.noseTip.x = kit.noseTip.x;
-					lastPositions.noseTip.y = kit.noseTip.y;
-				}
-			}
-		}
-		// If no significant changes, skip the update
-		if (!shouldUpdate) {
-			return;
-		}
 		// 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;
 		// Update scales
 		self.scales = {
 			head: {
 				x: baseScale * self.currentOffsets.head.sx,
@@ -381,15 +321,47 @@
 				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 facekit
 		self.updateAllFaceElements(facekit, 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);
+			// 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;
+			}
+			// Apply smoothing to the rotation
+			currentRotation = currentRotation + (angle - currentRotation) * 0.2;
+			return currentRotation;
+		}
+		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;
@@ -413,35 +385,12 @@
 ****/ 
 /**** 
 * Global Variables
 ****/ 
-var debugMode = false; // DEBUG MODE DEBUG MODE  DEBUG MODE 
-var bypassTracking = false; // Global variable to bypass face tracking
+var debugMode = true; // DEBUG MODE DEBUG MODE  DEBUG MODE 
+var bypassTracking = true; // Global variable to bypass face tracking
 var currentEyeDistance = 200; // Global variable to store current eye distance
-var movementThreshold = 2; // Threshold for position changes (in pixels)
-var lastPositions = {
-	// Last tracked positions
-	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
-	}
-};
+var currentRotation = 0; // Current smoothed rotation value
 var trollFaceOffsets = [{
 	head: {
 		x: 0,
 		y: 0,
@@ -681,20 +630,28 @@
 		trackingStoppedCounter = trackingStoppedDelay; // Reset delay
 	}
 	// Update troll face position to match real face
 	if (isTrackingFace) {
-		// Only update position if the change exceeds the threshold
-		var dx = Math.abs(targetPosition.x - facekit.noseTip.x);
-		var dy = Math.abs(targetPosition.y - facekit.noseTip.y);
-		if (dx > movementThreshold || dy > movementThreshold) {
-			// Update target position
-			targetPosition.x = facekit.noseTip.x;
-			targetPosition.y = facekit.noseTip.y;
-			// Apply faster smoothing for better responsiveness
-			trollFace.x = trollFace.x + (targetPosition.x - trollFace.x) * 0.5;
-			trollFace.y = trollFace.y + (targetPosition.y - trollFace.y) * 0.5;
-			trollFace.isTweening = true; // TEMP DEBUG
-		}
+		// 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
 		trollFace.updateFacePosition();
 		trollFace.isCentered = false;
 		trollFace.isCentering = false;
@@ -802,9 +759,11 @@
 				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);
 			}
-			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)));
+			// 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