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
This commit is contained in:
dilgenfritz 2025-11-04 20:24:41 -06:00
parent dbadd4a828
commit 9a8d4b5432
3 changed files with 540 additions and 28 deletions

View File

@ -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: {

View File

@ -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 = `
<div class="verification-container">
<div class="verification-header">
<h3>🔍 Position Verification Required</h3>
<p>${verificationData?.instructions || 'Follow the position instructions and maintain pose'}</p>
</div>
<div class="verification-video-container">
<video id="verification-video" autoplay muted playsinline></video>
<div class="verification-overlay-text">
<div class="position-instructions">
${verificationData?.verificationInstructions || 'Assume required position'}
</div>
</div>
</div>
<div class="verification-timer" id="verification-timer" style="display: none;">
<div class="verification-progress" id="verification-progress">
<div class="progress-bar" id="verification-progress-bar"></div>
</div>
<div class="timer-text">Verification time: <span id="verification-time">${verificationData?.verificationDuration || 30}</span>s</div>
<div class="verification-status" id="verification-status">Get in position...</div>
</div>
<div class="verification-controls">
<button id="verification-start-btn" class="btn btn-primary">
🔍 Start Verification
</button>
<button id="verification-close-btn" class="btn btn-secondary">
Abandon Task
</button>
</div>
${verificationData?.verificationText ? `<div class="verification-task-text">${verificationData.verificationText}</div>` : ''}
</div>
`;
// 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
*/

View File

@ -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 = `
<div class="training-task">
<h3>🎉 Training Complete!</h3>
<p>Congratulations! You have completed your training session.</p>
<div class="training-controls">
<button onclick="location.reload()" class="complete-btn">Start New Session</button>
<button onclick="selectMode('training-academy')" class="next-btn">Select Different Mode</button>
</div>
</div>
`;
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 @@
</div>
</div>
`;
} else if (step.type === 'verification-required') {
stepHtml = `
<div class="training-task scenario-verification">
<h3>🔍 ${scenario.title} - Verification Required</h3>
<div class="scenario-story">
${step.story}
</div>
<div class="verification-requirements">
<h4>📋 Position Requirements:</h4>
<p class="verification-instructions">${step.verificationInstructions}</p>
<p class="verification-text">${step.verificationText}</p>
<p class="verification-duration"><strong>Duration:</strong> ${step.verificationDuration} seconds</p>
</div>
<div class="verification-warning">
<p>⚠️ <strong>Warning:</strong> You must maintain the required position for the full duration. The webcam will verify your compliance.</p>
</div>
<div class="training-controls">
<button onclick="startPositionVerification('${step.nextStep}', ${step.verificationDuration}, '${step.verificationInstructions}', '${step.verificationText}')" class="action-btn">🔍 Start Position Verification</button>
<button onclick="selectScenarioChoice('${step.nextStep}')" class="skip-btn">Skip (Failure)</button>
</div>
</div>
`;
} else if (step.type === 'ending') {
stepHtml = `
<div class="training-task scenario-ending">
@ -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');