From 9a8d4b5432ab45c7767133176c0b53e7bcb6c915 Mon Sep 17 00:00:00 2001 From: dilgenfritz Date: Tue, 4 Nov 2025 20:24:41 -0600 Subject: [PATCH] Fix training academy task completion and prevent unwanted default task cycling - Fixed completion behavior to return to mode selection instead of cycling through tasks - Added explicit training-academy mode handling to load proper scenario data - Prevented default focus-hold tasks (edge-focus-training, etc.) from being loaded - Added task filtering to block unwanted default tasks - Enhanced gameData protection with getter/setter to prevent task replacement - Improved error handling to return to mode selection on invalid scenarios - Training academy now properly loads scenario adventures with full interactive data - Fixed scenario-training-regimen loading to include proper step data --- src/data/modes/humiliationGameData.js | 19 +- src/features/webcam/webcamManager.js | 340 ++++++++++++++++++++++++++ training-academy.html | 209 ++++++++++++++-- 3 files changed, 540 insertions(+), 28 deletions(-) diff --git a/src/data/modes/humiliationGameData.js b/src/data/modes/humiliationGameData.js index 4627242..37001c9 100644 --- a/src/data/modes/humiliationGameData.js +++ b/src/data/modes/humiliationGameData.js @@ -90,9 +90,12 @@ const humiliationGameData = { nextStep: "verbal_escalation" }, position_punishment: { - type: 'text', + type: 'verification-required', mood: 'authoritative', story: "The supervisor points to the floor aggressively. 'Get on your knees immediately. Hands behind your back, head down in shame. This is your punishment position. You will hold this degrading pose while thinking about what a complete failure you are.'", + verificationInstructions: "Get on your knees with hands behind your back and head down", + verificationDuration: 45, + verificationText: "Maintain this humiliating position while the camera captures your shame", nextStep: "position_escalation" }, mirror_punishment: { @@ -223,19 +226,21 @@ const humiliationGameData = { nextStep: "punishment_completion" }, extreme_position_punishment: { - type: 'action', + type: 'verification-required', mood: 'physical_degradation', story: "The supervisor forces you into the most degrading positions possible. 'Lower! More shameful! Show how worthless you are through your posture!'", - actionText: "Assume the most degrading and shameful physical positions possible", - duration: 240, + verificationInstructions: "Assume the most degrading physical position you can imagine", + verificationDuration: 60, + verificationText: "Hold your most shameful position while the camera documents your disgrace", nextStep: "punishment_completion" }, endurance_position_punishment: { - type: 'action', + type: 'verification-required', mood: 'endurance_humiliation', story: "The supervisor makes you hold degrading positions for extended time. 'Hold that shameful pose longer! Feel how degrading it is!'", - actionText: "Hold degrading positions for extended periods as punishment", - duration: 360, + verificationInstructions: "Get on your knees, hands behind back, head down in complete submission", + verificationDuration: 90, + verificationText: "Maintain this humiliating position for the full duration as your punishment", nextStep: "punishment_completion" }, mirror_position_punishment: { diff --git a/src/features/webcam/webcamManager.js b/src/features/webcam/webcamManager.js index bd740a4..6e62bc2 100644 --- a/src/features/webcam/webcamManager.js +++ b/src/features/webcam/webcamManager.js @@ -1133,6 +1133,346 @@ class WebcamManager { return true; } + /** + * Start webcam verification mode for position verification + */ + async startVerificationMode(verificationData) { + console.log('🔍 Starting webcam verification mode'); + + // Request camera if not already active + if (!this.isActive) { + const accessGranted = await this.requestCameraAccess(); + if (!accessGranted) { + console.warn('📷 Camera access required for verification mode'); + return false; + } + } + + // Track verification start + if (this.game && this.game.trackWebcamVerification) { + this.game.trackWebcamVerification(true); + } + + // Show verification interface + this.showVerificationInterface(verificationData); + return true; + } + + /** + * Display verification interface - webcam feed with position verification + */ + showVerificationInterface(verificationData) { + // Create verification overlay + const overlay = document.createElement('div'); + overlay.id = 'verification-overlay'; + overlay.innerHTML = ` +
+
+

🔍 Position Verification Required

+

${verificationData?.instructions || 'Follow the position instructions and maintain pose'}

+
+
+ +
+
+ ${verificationData?.verificationInstructions || 'Assume required position'} +
+
+
+ +
+ + +
+ ${verificationData?.verificationText ? `
${verificationData.verificationText}
` : ''} +
+ `; + + // Style the verification overlay + overlay.style.cssText = ` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.95); + display: flex; + justify-content: center; + align-items: center; + z-index: 99999; + font-family: Arial, sans-serif; + `; + + const containerStyle = ` + background: linear-gradient(135deg, #2c3e50, #34495e); + border: 2px solid #e74c3c; + border-radius: 15px; + padding: 30px; + max-width: 700px; + width: 95%; + text-align: center; + box-shadow: 0 15px 35px rgba(0, 0, 0, 0.7); + color: #ecf0f1; + `; + + const videoStyle = ` + width: 100%; + max-width: 500px; + height: auto; + border-radius: 10px; + margin: 15px 0; + transform: scaleX(-1); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); + border: 2px solid #e74c3c; + position: relative; + `; + + const buttonStyle = ` + margin: 10px; + padding: 12px 24px; + border: none; + border-radius: 8px; + font-size: 16px; + cursor: pointer; + transition: all 0.3s ease; + background: linear-gradient(135deg, #e74c3c, #c0392b); + color: white; + font-weight: 600; + min-width: 140px; + `; + + // Apply styles + overlay.querySelector('.verification-container').style.cssText = containerStyle; + overlay.querySelector('#verification-video').style.cssText = videoStyle; + overlay.querySelectorAll('button').forEach(btn => { + btn.style.cssText = buttonStyle; + }); + + // Style specific elements + const startBtn = overlay.querySelector('#verification-start-btn'); + const closeBtn = overlay.querySelector('#verification-close-btn'); + + if (closeBtn) { + closeBtn.style.background = 'linear-gradient(135deg, #95a5a6, #7f8c8d)'; + } + + document.body.appendChild(overlay); + + // Connect video stream to verification video element + const verificationVideo = overlay.querySelector('#verification-video'); + if (this.stream && verificationVideo) { + verificationVideo.srcObject = this.stream; + } + + // Add event listeners + startBtn.addEventListener('click', () => { + console.log('🔍 Starting verification process'); + this.startVerificationTimer(verificationData, overlay); + }); + + closeBtn.addEventListener('click', () => { + console.log('❌ Verification abandoned - triggering game over'); + this.closeVerificationMode(); + this.showCondescendingGameOver(); + }); + + console.log('🔍 Verification interface displayed'); + } + + /** + * Start verification timer with countdown and auto-capture + */ + startVerificationTimer(verificationData, overlay) { + const duration = verificationData?.verificationDuration || 30; + const timerDisplay = overlay.querySelector('#verification-time'); + const progressBar = overlay.querySelector('#verification-progress-bar'); + const timerElement = overlay.querySelector('#verification-timer'); + const statusElement = overlay.querySelector('#verification-status'); + const startBtn = overlay.querySelector('#verification-start-btn'); + + // Hide start button and show timer + if (startBtn) startBtn.style.display = 'none'; + if (timerElement) timerElement.style.display = 'block'; + + // Preparation phase (10 seconds) + let prepTime = 10; + if (statusElement) statusElement.textContent = `Get in position now! ${prepTime}s`; + + const prepTimer = setInterval(() => { + prepTime--; + if (statusElement) statusElement.textContent = `Get in position now! ${prepTime}s`; + + if (prepTime <= 0) { + clearInterval(prepTimer); + this.startMainVerification(duration, overlay); + } + }, 1000); + } + + /** + * Start main verification phase with position holding + */ + startMainVerification(duration, overlay) { + const timerDisplay = overlay.querySelector('#verification-time'); + const progressBar = overlay.querySelector('#verification-progress-bar'); + const statusElement = overlay.querySelector('#verification-status'); + + let timeLeft = duration; + if (statusElement) { + statusElement.textContent = 'HOLD POSITION - Being verified...'; + statusElement.style.color = '#e74c3c'; + statusElement.style.fontWeight = 'bold'; + } + + const timer = setInterval(() => { + timeLeft--; + + // Update timer display + if (timerDisplay) { + timerDisplay.textContent = timeLeft; + } + + // Update progress bar + if (progressBar) { + const progress = ((duration - timeLeft) / duration) * 100; + progressBar.style.width = progress + '%'; + } + + // Update status + if (statusElement && timeLeft > 0) { + statusElement.textContent = `HOLD POSITION - ${timeLeft}s remaining`; + } + + // Verification complete + if (timeLeft <= 0) { + clearInterval(timer); + this.completeVerification(overlay); + } + }, 1000); + + // Store timer reference for cleanup + this.verificationTimer = timer; + } + + /** + * Complete verification process with photo capture + */ + completeVerification(overlay) { + const statusElement = overlay.querySelector('#verification-status'); + + if (statusElement) { + statusElement.textContent = 'Capturing verification photo...'; + statusElement.style.color = '#27ae60'; + } + + // Auto-capture verification photo + setTimeout(() => { + this.captureVerificationPhoto(overlay); + }, 1000); + } + + /** + * Capture verification photo and complete process + */ + captureVerificationPhoto(overlay) { + const video = overlay.querySelector('#verification-video'); + const statusElement = overlay.querySelector('#verification-status'); + + if (video && video.videoWidth > 0) { + // Create canvas and capture frame + const canvas = document.createElement('canvas'); + canvas.width = video.videoWidth; + canvas.height = video.videoHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(video, 0, 0); + + // Convert to base64 + const photoData = canvas.toDataURL('image/jpeg', 0.8); + + // Store verification photo + const verificationPhoto = { + timestamp: new Date().toISOString(), + data: photoData, + type: 'position_verification' + }; + + // Save to localStorage + const existingPhotos = JSON.parse(localStorage.getItem('verificationPhotos') || '[]'); + existingPhotos.push(verificationPhoto); + localStorage.setItem('verificationPhotos', JSON.stringify(existingPhotos)); + + console.log('📸 Verification photo captured and stored'); + + if (statusElement) { + statusElement.textContent = 'Verification COMPLETE - Position documented'; + statusElement.style.color = '#27ae60'; + } + + // Complete verification after 2 seconds + setTimeout(() => { + this.finalizeVerification(overlay); + }, 2000); + } else { + console.error('❌ Failed to capture verification photo'); + if (statusElement) { + statusElement.textContent = 'Verification failed - try again'; + statusElement.style.color = '#e74c3c'; + } + } + } + + /** + * Finalize verification and proceed + */ + finalizeVerification(overlay) { + console.log('✅ Position verification completed successfully'); + + this.closeVerificationMode(); + + // Notify completion + const event = new CustomEvent('verificationComplete', { + detail: { success: true } + }); + document.dispatchEvent(event); + } + + /** + * Close verification mode + */ + closeVerificationMode() { + console.log('🔚 Closing verification mode'); + + // Clear timer if running + if (this.verificationTimer) { + clearInterval(this.verificationTimer); + this.verificationTimer = null; + } + + // Track verification end + if (this.game && this.game.trackWebcamVerification) { + this.game.trackWebcamVerification(false); + } + + // Remove verification overlay + const overlay = document.getElementById('verification-overlay'); + if (overlay) { + overlay.remove(); + } + + // Stop camera stream + this.stopCamera(); + } + /** * Display mirror interface - webcam feed without photo capture */ diff --git a/training-academy.html b/training-academy.html index 020c063..86d1a77 100644 --- a/training-academy.html +++ b/training-academy.html @@ -1244,6 +1244,19 @@ isScenario: true })); console.log(`📋 Loaded ${trainingTasks.length} punishment scenarios directly from humiliation data`); + } else if (selectedTrainingMode === 'training-academy' && window.trainingGameData && window.trainingGameData.scenarios) { + console.log('🎓 Training Academy mode detected - loading training scenarios directly'); + trainingTasks = window.trainingGameData.scenarios.map(scenario => ({ + id: scenario.id, + text: scenario.text, + difficulty: scenario.difficulty, + type: 'main', + interactiveType: scenario.interactiveType, + interactiveData: scenario.interactiveData, + isScenario: true + })); + console.log(`📋 Loaded ${trainingTasks.length} training scenarios directly from training data`); + console.log('🔍 First scenario interactive data:', trainingTasks[0]?.interactiveData ? 'Present' : 'Missing'); } else if (selectedTrainingMode === 'photography-studio' && window.dressUpGameData && window.dressUpGameData.scenarios) { console.log('📸 Photography Studio mode detected - loading dress-up scenarios directly'); trainingTasks = window.dressUpGameData.scenarios.map(scenario => ({ @@ -1283,10 +1296,12 @@ } else if (selectedTrainingMode === 'endurance-trials' && window.enduranceGameData && window.enduranceGameData.scenarios) { fallbackData = window.enduranceGameData.scenarios; console.log(`📋 Using endurance data fallback for endurance-trials mode`); - } else if (window.trainingGameData && window.trainingGameData.scenarios) { + } else if (selectedTrainingMode === 'training-academy' && window.trainingGameData && window.trainingGameData.scenarios) { + // Only use training data scenarios if they exist and the mode is specifically training-academy fallbackData = window.trainingGameData.scenarios; - console.log(`📋 Using training data fallback for ${selectedTrainingMode} mode`); + console.log(`📋 Using training data fallback for training-academy mode`); } + // Removed the generic fallback to prevent using wrong task types if (fallbackData) { trainingTasks = fallbackData.map(scenario => ({ @@ -1304,18 +1319,34 @@ // Check if we have any tasks to work with if (trainingTasks.length === 0) { - console.error('❌ No training tasks available'); - alert('Error: No training scenarios found. Please check your game data.'); + console.error('❌ No training tasks available for mode:', selectedTrainingMode); + alert(`Error: No training scenarios found for ${selectedTrainingMode} mode. This training mode may not have content available yet.`); + + // Return to mode selection instead of failing + returnToModeSelection(); return; } // Filter out any invalid tasks and ensure proper structure const validTrainingTasks = trainingTasks.filter(task => { const isValid = task && task.id && task.text && task.interactiveType; + + // Also exclude the default focus-hold training tasks that shouldn't be in scenarios + const isUnwantedDefaultTask = task.id === 'edge-focus-training' || + task.id === 'stroking-endurance-training' || + task.id === 'extended-edging-session'; + + if (isUnwantedDefaultTask) { + console.warn('⚠️ Filtering out unwanted default task:', task.id); + return false; + } + if (!isValid) { console.warn('⚠️ Filtering out invalid task:', task); + return false; } - return isValid; + + return true; }).map(task => { // Ensure each task has all required properties for the game engine return { @@ -1408,6 +1439,31 @@ // Set training tasks as the active tasks window.gameData.mainTasks = trainingTasks; console.log('📋 Set training tasks as active game data'); + + // Create a robust getter/setter to prevent unwanted task replacement + const tasksList = [...trainingTasks]; + Object.defineProperty(window.gameData, 'mainTasks', { + get: function() { + return this._trainingTasks || tasksList; + }, + set: function(value) { + // Filter out unwanted default tasks if they somehow get added + if (Array.isArray(value)) { + const filteredTasks = value.filter(task => + task.id !== 'edge-focus-training' && + task.id !== 'stroking-endurance-training' && + task.id !== 'extended-edging-session' + ); + this._trainingTasks = filteredTasks.length > 0 ? filteredTasks : tasksList; + console.log('🛡️ Training Academy: Filtered tasks, keeping', this._trainingTasks.length, 'valid tasks'); + } else { + this._trainingTasks = tasksList; + } + } + }); + + // Initialize with training tasks + window.gameData.mainTasks = trainingTasks; } // 3.5. Override interactive task display to prevent UI errors @@ -1800,22 +1856,63 @@ const existing = document.getElementById('training-task-display'); if (existing) existing.remove(); - // Show completion message - const completionContainer = document.querySelector('.game-content') || document.body; - const completionDiv = document.createElement('div'); - completionDiv.id = 'training-completion'; - completionDiv.style.cssText = 'position: fixed; top: 30%; left: 50%; transform: translateX(-50%); z-index: 9999; width: 80%; max-width: 600px;'; - completionDiv.innerHTML = ` -
-

🎉 Training Complete!

-

Congratulations! You have completed your training session.

-
- - -
-
- `; - completionContainer.appendChild(completionDiv); + // Clear the game container + const gameContainer = document.getElementById('game-container'); + if (gameContainer) gameContainer.innerHTML = ''; + + // Return to mode selection by resetting the interface + returnToModeSelection(); + } + + function returnToModeSelection() { + console.log('🔄 Returning to training mode selection...'); + + // Clear any active training state + selectedTrainingMode = null; + currentScenarioTask = null; + currentScenarioStep = 'start'; + + // Clear any game overrides and restore original state + if (window.game) { + // Stop the game session + window.game.gameState.isRunning = false; + window.game.gameState.isPaused = false; + + // Clear current task + window.game.gameState.currentTask = null; + window.game.currentTask = null; + + // Reset any overridden methods if needed + if (window.gameData && window.gameData.originalMainTasks) { + window.gameData.mainTasks = window.gameData.originalMainTasks; + } + } + + // Show setup interface again + document.querySelector('.academy-header').style.display = 'block'; + document.querySelector('.library-status').style.display = 'block'; + document.querySelector('.training-controls').style.display = 'block'; + document.querySelector('.academy-start-controls').style.display = 'block'; + + // Hide game interface + const gameInterface = document.getElementById('gameInterface'); + gameInterface.style.display = 'none'; + + // Reset training mode cards + document.querySelectorAll('.training-mode-card').forEach(card => { + card.classList.remove('selected'); + }); + + // Disable start button until new mode is selected + document.getElementById('startTrainingBtn').disabled = true; + + // Restore video controls visibility + const videoControls = document.getElementById('videoControlsOverlay'); + if (videoControls) { + videoControls.style.opacity = '1'; + } + + console.log('✅ Returned to training mode selection screen'); } // Scenario Adventure Display System @@ -1984,6 +2081,28 @@ `; + } else if (step.type === 'verification-required') { + stepHtml = ` +
+

🔍 ${scenario.title} - Verification Required

+
+ ${step.story} +
+
+

📋 Position Requirements:

+

${step.verificationInstructions}

+

${step.verificationText}

+

Duration: ${step.verificationDuration} seconds

+
+
+

⚠️ Warning: You must maintain the required position for the full duration. The webcam will verify your compliance.

+
+
+ + +
+
+ `; } else if (step.type === 'ending') { stepHtml = `
@@ -2085,6 +2204,54 @@ }, 100); } + function startPositionVerification(nextStep, duration, instructions, verificationText) { + console.log('🔍 Starting position verification for', duration, 'seconds'); + + // Store next step for completion callback + window.trainingAcademyNextStep = nextStep; + + // Create verification data for the webcam system + const verificationData = { + instructions: "Position verification required for training compliance", + verificationInstructions: instructions, + verificationText: verificationText, + verificationDuration: duration, + onComplete: function() { + console.log('🔍 Position verification completed, proceeding to:', window.trainingAcademyNextStep); + proceedToNextStep(window.trainingAcademyNextStep); + } + }; + + // Use the webcam manager to start verification mode + if (window.game && window.game.webcamManager) { + console.log('🎥 Starting webcam verification mode...'); + window.game.webcamManager.startVerificationMode(verificationData).then((success) => { + if (success) { + console.log('🔍 Verification mode started successfully'); + + // Listen for verification completion + document.addEventListener('verificationComplete', function(event) { + if (event.detail.success) { + console.log('✅ Verification completed successfully'); + proceedToNextStep(nextStep); + } + }, { once: true }); + + } else { + console.warn('⚠️ Verification mode failed to start'); + // Show failure and allow skip or retry + alert('Camera access required for position verification. Please enable camera access.'); + } + }).catch(error => { + console.error('❌ Verification mode failed:', error); + alert('Failed to start position verification. Check camera access.'); + }); + } else { + console.warn('⚠️ Webcam manager not available'); + alert('Webcam verification system not available. Cannot proceed with position verification.'); + } + } + function startScenarioAction(nextStep, duration) { console.log('🎬 Starting scenario action for', duration, 'seconds');