MOLOCH - Replayability System Specification

Technical Design Document v1.0


1. OVERVIEW

Moloch is designed for infinite replayability through:

  • Procedural narrative variation
  • Persistent cross-session memory
  • Social ghost mode integration
  • Time-pressure urgency
  • Meta-progression rewards

Core Loop: Each playthrough reveals new layers while acknowledging player history.


2. VARIATION ON EACH PLAY

2.1 Rotating Villain System

Concept: The “true antagonist” shifts each playthrough, changing NPC roles, dialogue, and available endings.

Villain Pool:

Villain IDRoleMotivationUnique Ending
villain_elderVillage ElderPreserve tradition at any costending_sacrifice
villain_strangerMysterious StrangerEscape their own dark pastending_betrayal
villain_villagerTrusted VillagerProtect family secretending_damnation
villain_playerThe Player ThemselvesUnknowingly the sourceending_truth
villain_wellThe Well EntityFeed its endless hungerending_ascension
villain_noneNo true villainCycle of misunderstandingending_redemption

Selection Algorithm:

class VillainRotationSystem {
  constructor(metaData) {
    this.discoveredEndings = metaData.endingsDiscovered;
    this.playSessionCount = metaData.playSessionCount;
    this.previousVillains = metaData.previousPlaythroughs.map(p => p.villain);
  }
  
  selectVillain() {
    const candidates = this.getCandidateVillains();
    const weights = this.calculateWeights(candidates);
    return this.weightedRandomSelect(candidates, weights);
  }
  
  getCandidateVillains() {
    // First 3 plays: guaranteed different villains
    if (this.playSessionCount <= 3) {
      return VILLAIN_POOL.filter(v => !this.previousVillains.includes(v.id));
    }
    
    // After 3 plays: all villains available, weighted toward unseen
    return VILLAIN_POOL;
  }
  
  calculateWeights(candidates) {
    return candidates.map(villain => {
      let weight = 1.0;
      
      // Boost unseen villains
      if (!this.previousVillains.includes(villain.id)) {
        weight *= 2.5;
      }
      
      // Reduce recently played villains
      const lastPlayed = this.previousVillains.lastIndexOf(villain.id);
      if (lastPlayed !== -1) {
        const playsAgo = this.previousVillains.length - lastPlayed;
        weight *= Math.min(playsAgo / 3, 1.0);
      }
      
      // Special: villain_player only after 5+ plays
      if (villain.id === 'villain_player' && this.playSessionCount < 5) {
        weight = 0;
      }
      
      // Special: villain_well only after seeing 3+ endings
      if (villain.id === 'villain_well' && this.discoveredEndings.length < 3) {
        weight = 0;
      }
      
      return weight;
    });
  }
}

Villain Impact on Gameplay:

const VILLAIN_CONFIG = {
  villain_elder: {
    // Changes NPC trust curves
    trustModifiers: {
      elder_marcus: +30, // Starts trusted
      villager_sarah: -10 // Suspicious of elder
    },
    // Hides specific clues
    hiddenClues: ['stranger_true_identity'],
    // Unique dialogue trees
    exclusiveDialogue: ['dlg_elder_defends_tradition'],
    // Affects which endings possible
    availableEndings: ['ending_sacrifice', 'ending_cycle', 'ending_redemption'],
    // Changes well behavior
    wellModifier: { drainRate: 0.8 } // Slower drain (elder controls ritual)
  },
  
  villain_stranger: {
    trustModifiers: {
      stranger_cain: +20, // Seems helpful at first
      elder_marcus: -20 // Elder distrusts outsider
    },
    hiddenClues: ['elder_ritual_knowledge'],
    exclusiveDialogue: ['dlg_stranger_seeks_ally'],
    availableEndings: ['ending_betrayal', 'ending_escape', 'ending_truth'],
    wellModifier: { drainRate: 1.2 } // Faster drain (stranger accelerates)
  }
};

2.2 Random Event System

Event Categories:

CategoryFrequencyImpact
ambientEvery 2-3 minutesAtmosphere, minor trust shifts
contextualTriggered by choicesSignificant branch changes
critical1-2 per playthroughMajor revelation or choice
rare~10% chanceUnique scenes, easter eggs

Event Examples:

const RANDOM_EVENTS = {
  // Ambient events
  'event_wind_whispers': {
    category: 'ambient',
    condition: (state) => state.well.waterLevel < 50,
    text: "The wind carries whispers from the well... voices you almost recognize.",
    effect: (state) => { state.npcs.all.trustScore -= 2; }
  },
  
  // Contextual events
  'event_villager_argument': {
    category: 'contextual',
    trigger: 'after_choice_betray_confidence',
    text: "You overhear two villagers arguing about trust... and your name is mentioned.",
    choices: [
      { id: 'confront', text: 'Confront them', effect: 'reveal_betrayal' },
      { id: 'listen', text: 'Listen from shadows', effect: 'gain_info' },
      { id: 'ignore', text: 'Walk away', effect: 'status_quo' }
    ]
  },
  
  // Critical events
  'event_well_speaks': {
    category: 'critical',
    condition: (state) => state.well.waterLevel < 15 && state.playtime > 1800,
    text: "THE WELL SPEAKS. Its voice is your own, but twisted. 'How many times will you fail?'",
    effect: (state) => { state.meta.secretFlags.heardWellSpeak = true; }
  },
  
  // Rare events
  'event_developer_ghost': {
    category: 'rare',
    chance: 0.05,
    condition: (state) => state.meta.playSessionCount > 10,
    text: "For a moment, the screen flickers. You see code beneath the world. A message: 'We made this for you.'",
    effect: (state) => { state.meta.secretFlags.foundDevelopersNote = true; }
  }
};

2.3 Dynamic Dialogue Unlocks

Unlock Conditions:

const DIALOGUE_UNLOCKS = {
  // Based on history
  'dlg_returning_reference': {
    unlock: (meta) => meta.playSessionCount > 1,
    template: "You've been here {{sessionCount}} times now. What pattern are you trying to break?"
  },
  
  // Based on endings seen
  'dlg_ending_wisdom': {
    unlock: (meta) => meta.endingsDiscovered.length >= 2,
    template: "You've seen how this ends. Yet you return. Hope? Or merely habit?"
  },
  
  // Based on choice patterns
  'dlg_trust_issues': {
    unlock: (meta) => {
      const betrayals = meta.playerTendencies.betrayChoices;
      const total = meta.playerTendencies.totalChoices;
      return betrayals / total > 0.4;
    },
    template: "You betray easily. I wonder... will you betray yourself?"
  },
  
  // Based on playtime
  'dlg_time_observation': {
    unlock: (meta) => meta.totalPlaytime > 36000, // 10+ hours
    template: "You've spent {{hours}} hours in this place. What are you searching for?"
  },
  
  // Based on specific combinations
  'dlg_secret_knowledge': {
    unlock: (meta) => {
      return meta.endingsDiscovered.includes('ending_sacrifice') &&
             meta.endingsDiscovered.includes('ending_betrayal') &&
             meta.secretFlags.discoveredTrueName;
    },
    template: "You know the truth of sacrifice AND betrayal. You know MY name. What will you do with this knowledge?"
  }
};

3. GHOST MODE SPECIFICATION

3.1 Overview

Ghost Mode reveals aggregate player behavior without spoiling outcomes. Players see faint “ghosts” of others’ choices.

3.2 Data Aggregation

Collection Strategy:

const GHOST_DATA_SCHEMA = {
  // Per-choice statistics
  choiceStats: {
    'choice_trust_elder': {
      totalSelections: 15420,
      firstPlaySelections: 8900,
      returningPlaySelections: 6520,
      avgWaterLevelAtChoice: 67.3,
      commonNextChoice: 'choice_accept_ritual'
    }
  },
  
  // Path statistics
  pathStats: {
    'path_sacrifice_route': {
      completionRate: 0.23,
      avgPlaytime: 4200,
      commonEndings: ['ending_sacrifice', 'ending_cycle']
    }
  },
  
  // Temporal data
  timeStats: {
    avgSessionLength: 3600,
    commonQuitPoints: ['node_well_confrontation', 'node_betrayal_reveal'],
    completionRate: 0.67
  }
};

Privacy Protection:

  • All data anonymized (no player IDs)
  • Minimum sample size: 100 players before showing stats
  • No timestamps that could identify sessions
  • Aggregate only - no individual choice trails

3.3 Visual Design

Ghost Appearance:

/* Ghost choice indicator */
.ghost-choice {
  opacity: 0.3;
  color: #8a8a8a;
  font-style: italic;
  position: relative;
}
 
.ghost-choice::before {
  content: '👤';
  opacity: 0.4;
  margin-right: 8px;
}
 
/* Percentage display */
.ghost-percentage {
  font-size: 0.8em;
  color: #666;
  margin-left: 12px;
}
 
/* Ghost trail effect */
@keyframes ghost-fade {
  0% { opacity: 0.4; }
  50% { opacity: 0.2; }
  100% { opacity: 0.4; }
}
 
.ghost-trail {
  animation: ghost-fade 3s infinite;
}

UI Mockup:

[Choice A] Trust the Elder
         👤 65% chose this

[Choice B] Trust the Stranger  
         👤 23% chose this

[Choice C] Trust No One
         👤 12% chose this

3.4 Display Modes

const GHOST_MODES = {
  OFF: {
    id: 'off',
    description: 'No ghost data shown',
    display: () => null
  },
  
  SUBTLE: {
    id: 'subtle',
    description: 'Faint ghost indicators only',
    display: (choiceId, stats) => {
      if (stats.percentage > 50) {
        return { indicator: '👤', opacity: 0.2 };
      }
      return null;
    }
  },
  
  STANDARD: {
    id: 'standard',
    description: 'Percentage shown for all choices',
    display: (choiceId, stats) => {
      return {
        indicator: '👤',
        percentage: Math.round(stats.percentage),
        opacity: 0.3
      };
    }
  },
  
  DETAILED: {
    id: 'detailed',
    description: 'Full statistics including trends',
    display: (choiceId, stats) => {
      return {
        indicator: '👤',
        percentage: Math.round(stats.percentage),
        trend: stats.trend, // 'rising', 'falling', 'stable'
        sampleSize: stats.totalSelections,
        opacity: 0.4
      };
    }
  }
};

3.5 Toggle System

class GhostModeManager {
  constructor() {
    this.currentMode = this.loadPreference() || 'standard';
    this.isEnabled = this.loadEnabledState() ?? true;
  }
  
  toggle() {
    this.isEnabled = !this.isEnabled;
    this.saveEnabledState();
    this.emit('ghostModeChanged', this.isEnabled);
  }
  
  setMode(mode) {
    if (GHOST_MODES[mode]) {
      this.currentMode = mode;
      this.savePreference();
      this.emit('ghostModeChanged', mode);
    }
  }
  
  cycleMode() {
    const modes = Object.keys(GHOST_MODES);
    const currentIndex = modes.indexOf(this.currentMode);
    const nextIndex = (currentIndex + 1) % modes.length;
    this.setMode(modes[nextIndex]);
  }
  
  getDisplayForChoice(choiceId) {
    if (!this.isEnabled) return null;
    
    const stats = this.fetchGhostStats(choiceId);
    if (!stats || stats.sampleSize < 100) return null;
    
    return GHOST_MODES[this.currentMode].display(choiceId, stats);
  }
}

3.6 Anti-Spoiler Protection

const SPOILER_PROTECTION = {
  // Don't show ghost data for choices that lead directly to endings
  isEndingChoice: (choiceId) => {
    return ENDING_CHOICE_IDS.includes(choiceId);
  },
  
  // Delay ghost data for critical story moments
  shouldDelayGhost: (nodeId, playtime) => {
    const criticalNodes = ['reveal_true_villain', 'climax_confrontation'];
    if (criticalNodes.includes(nodeId)) {
      // Only show after player has made their choice
      return true;
    }
    return false;
  },
  
  // Blur percentages near major reveals
  blurThreshold: (waterLevel) => {
    if (waterLevel < 10) {
      return 0.5; // 50% opacity max near end
    }
    return 1.0;
  }
};

4. TIME PRESSURE MECHANICS

4.1 Well Drain System

Core Concept: The well drains in REAL TIME during play, creating genuine urgency.

Drain Mechanics:

class WellSystem {
  constructor() {
    this.waterLevel = 100;
    this.baseDrainRate = 0.5; // units per second
    this.drainMultiplier = 1.0;
    this.isPaused = false;
    this.lastUpdate = Date.now();
  }
  
  update() {
    if (this.isPaused) return;
    
    const now = Date.now();
    const deltaTime = (now - this.lastUpdate) / 1000;
    this.lastUpdate = now;
    
    const drainAmount = this.baseDrainRate * this.drainMultiplier * deltaTime;
    this.waterLevel = Math.max(0, this.waterLevel - drainAmount);
    
    this.checkThresholds();
  }
  
  // Choice-based drain acceleration
  applyChoiceDrain(choiceId) {
    const drainMap = {
      'choice_investigate_well': 5,      // Takes time
      'choice_quick_decision': 1,        // Fast
      'choice_ponder_deeply': 10,        // Slow, contemplative
      'choice_rush_action': 2,           // Hurried
      'choice_manipulate_ritual': 8,     // Complex
      'choice_flee_immediately': 3       // Urgent
    };
    
    const drain = drainMap[choiceId] || 3;
    this.waterLevel -= drain;
    
    this.recordDrainEvent(choiceId, drain);
  }
  
  // Villain-specific modifiers
  applyVillainModifier(villainId) {
    const modifiers = {
      'villain_elder': 0.8,    // Controlled, slower
      'villain_stranger': 1.2, // Chaotic, faster
      'villain_well': 1.5,     // The well hungers
      'villain_player': 1.0    // Neutral
    };
    
    this.drainMultiplier = modifiers[villainId] || 1.0;
  }
}

4.2 Visual Indicators

Water Level Display:

.well-indicator {
  position: fixed;
  bottom: 20px;
  right: 20px;
  width: 60px;
  height: 200px;
  background: #1a1a1a;
  border: 2px solid #444;
  border-radius: 4px;
  overflow: hidden;
}
 
.well-water {
  position: absolute;
  bottom: 0;
  width: 100%;
  background: linear-gradient(
    to top,
    #0d3b2e 0%,
    #1a5f4a 50%,
    #2d8b6a 100%
  );
  transition: height 0.3s ease;
}
 
/* Critical state */
.well-water.critical {
  background: linear-gradient(
    to top,
    #3b0d0d 0%,
    #5f1a1a 50%,
    #8b2d2d 100%
  );
  animation: pulse-critical 1s infinite;
}
 
@keyframes pulse-critical {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}
 
/* Percentage text */
.well-percentage {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  font-size: 14px;
  font-weight: bold;
  color: white;
  text-shadow: 0 0 4px rgba(0,0,0,0.8);
}

State-Based Visuals:

Water LevelVisual StateAudio
100-75%Calm, gentle ripplesAmbient drip
75-50%Slight turbulenceIncreased dripping
50-25%Churning, darkeningLow rumble
25-15%Violent, red-tingedUrgent pulsing
15-5%Critical, almost emptyHeartbeat sync
5-0%Empty, void revealedSilence, then…

4.3 Urgency Escalation

Narrative Urgency Integration:

const URGENCY_NARRATIVE = {
  // At 75% - subtle hints
  'urgency_75': {
    condition: (well) => well.waterLevel <= 75 && well.waterLevel > 74.5,
    dialogue: "The water seems... lower than before. Or is that just your imagination?",
    oneTime: true
  },
  
  // At 50% - clear concern
  'urgency_50': {
    condition: (well) => well.waterLevel <= 50 && well.waterLevel > 49.5,
    dialogue: "The well is half-empty. Time is running short. You can feel it.",
    oneTime: true
  },
  
  // At 25% - panic sets in
  'urgency_25': {
    condition: (well) => well.waterLevel <= 25 && well.waterLevel > 24.5,
    dialogue: "THE WATER FALLS. Whatever is coming, it comes soon.",
    oneTime: true,
    effect: (state) => { state.npcs.all.trustScore -= 10; } // Panic affects relationships
  },
  
  // At 10% - desperate
  'urgency_10': {
    condition: (well) => well.waterLevel <= 10 && well.waterLevel > 9.5,
    dialogue: "Almost gone. Almost time. What will you do with your final moments?",
    oneTime: true
  },
  
  // At 0% - the end
  'urgency_0': {
    condition: (well) => well.waterLevel <= 0,
    dialogue: "The well is dry. The waiting is over.",
    effect: (state) => { this.triggerEndingSequence(state); }
  }
};

4.4 Choice Time Cost Display

const CHOICE_TIME_COSTS = {
  'choice_investigate_well': {
    display: '⏱️ ~30 seconds',
    actualDrain: 5,
    description: 'Take time to examine the well carefully'
  },
  'choice_quick_decision': {
    display: '⏱️ ~5 seconds',
    actualDrain: 1,
    description: 'Act on instinct'
  },
  'choice_ponder_deeply': {
    display: '⏱️ ~1 minute',
    actualDrain: 10,
    description: 'Consider all implications'
  }
};
 
// UI Display
function renderChoice(choice) {
  const timeCost = CHOICE_TIME_COSTS[choice.id];
  return `
    <div class="choice">
      <span class="choice-text">${choice.text}</span>
      ${timeCost ? `<span class="time-cost">${timeCost.display}</span>` : ''}
    </div>
  `;
}

4.5 Pause Behavior

// Tab inactive = pause drain
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    wellSystem.pause();
    saveGame(); // Save on tab switch
  } else {
    wellSystem.resume();
  }
});
 
// Menu open = pause drain (optional)
function openMenu() {
  wellSystem.pause();
  showMenu();
}
 
function closeMenu() {
  hideMenu();
  wellSystem.resume();
}

5. NEW GAME+ FEATURES

5.1 Unlock Conditions

const NEW_GAME_PLUS = {
  unlockCondition: (meta) => {
    // Unlock after first completion
    return meta.endingsDiscovered.length >= 1;
  },
  
  // Or unlock immediately for returning players
  immediateUnlock: (meta) => meta.playSessionCount > 1
};

5.2 Knowledge Carryover

What Carries Over:

const CARRYOVER_SYSTEM = {
  // Meta-knowledge (always carried)
  persistentKnowledge: [
    'knowsRitualExists',
    'knowsVillagersDistrustOutsiders',
    'knowsWellIsSignificant'
  ],
  
  // Previous playthrough knowledge (conditional)
  conditionalKnowledge: {
    'knowsElderSecret': {
      condition: (meta) => meta.previousPlaythroughs.some(
        p => p.keyChoices.includes('choice_learn_elder_secret')
      ),
      unlockDialogue: 'dlg_recognize_elder_secret'
    },
    'knowsStrangerTrueName': {
      condition: (meta) => meta.secretFlags.discoveredTrueName,
      unlockDialogue: 'dlg_use_true_name'
    },
    'knowsRitualTruePurpose': {
      condition: (meta) => meta.endingsDiscovered.includes('ending_sacrifice'),
      unlockDialogue: 'dlg_understand_ritual'
    }
  },
  
  // Failed strategy knowledge
  attemptedStrategies: {
    carryover: true,
    usage: 'NPCs reference previous attempts',
    example: "You tried to persuade the Elder last time. It didn't work then either."
  }
};

5.3 New Dialogue Options

const NEW_GAME_PLUS_DIALOGUE = {
  // Returning player greetings
  'dlg_returning_greeting': {
    condition: (meta) => meta.playSessionCount > 1,
    variants: [
      "Back again? The well welcomes familiar faces.",
      "{{sessionCount}} times now. What are you seeking?",
      "The cycle continues. Do you remember what happened before?",
      "You've walked this path. Will you walk it differently?"
    ]
  },
  
  // Knowledge-based options
  'dlg_knowledge_option': {
    condition: (state, meta) => {
      return meta.previousPlaythroughs.some(p => 
        p.keyChoices.includes('choice_learn_secret')
      );
    },
    text: "[Use your knowledge] 'I know what you're hiding.'",
    effect: 'force_truth_reveal'
  },
  
  // Meta-commentary
  'dlg_meta_commentary': {
    condition: (meta) => meta.playSessionCount > 5,
    text: "You've seen how this ends. Multiple times. Yet you persist. Is it hope? Obsession? Or something else?"
  }
};

5.4 Hidden Path Unlocks

const HIDDEN_PATHS = {
  'path_true_ending': {
    unlockCondition: (meta) => {
      return meta.endingsDiscovered.length >= 6 &&
             meta.secretFlags.discoveredTrueName;
    },
    description: 'The path to understanding the true nature of the cycle',
    entryPoint: 'node_well_heart',
    requirements: ['knowsAllVillains', 'knowsTrueName', 'hasSeenEnough']
  },
  
  'path_break_cycle': {
    unlockCondition: (meta) => {
      return meta.playSessionCount >= 5 &&
             meta.playerTendencies.totalChoices > 100;
    },
    description: 'Attempt to do what no one has done before',
    entryPoint: 'node_final_choice_modified',
    requirements: ['persistentEnough', 'learnedPatterns']
  },
  
  'path_developer_room': {
    unlockCondition: (meta) => {
      return meta.secretFlags.foundDevelopersNote &&
             meta.playSessionCount >= 10;
    },
    description: 'A hidden message from the creators',
    entryPoint: 'node_behind_the_code',
    requirements: ['foundTheSign', 'earnedAccess']
  }
};

5.5 Retry Failed Strategies

class StrategyRetrySystem {
  constructor(meta) {
    this.attemptedStrategies = meta.newGamePlus?.attemptedStrategies || [];
    this.failedStrategies = this.identifyFailures();
  }
  
  identifyFailures() {
    return this.attemptedStrategies.filter(strategy => {
      // A strategy "failed" if it didn't lead to desired outcome
      return strategy.outcome !== strategy.intendedOutcome;
    });
  }
  
  canRetry(strategyId) {
    return this.failedStrategies.some(s => s.id === strategyId);
  }
  
  getRetryDialogue(strategyId) {
    const strategy = this.failedStrategies.find(s => s.id === strategyId);
    return `You tried to ${strategy.description} before. It didn't work then. But circumstances have changed...`;
  }
  
  getModifiedOutcome(strategyId, currentState) {
    // Some strategies work on retry with different context
    const baseOutcome = this.getBaseOutcome(strategyId);
    const contextModifier = this.calculateContextModifier(currentState);
    
    return this.applyModifier(baseOutcome, contextModifier);
  }
}

6. ACHIEVEMENT / DISCOVERY SYSTEM

6.1 Endings Catalog

const ENDINGS = {
  // Standard endings
  'ending_sacrifice': {
    name: 'The Sacrifice',
    description: 'You gave yourself to save others',
    difficulty: 'medium',
    hint: 'Trust can demand the ultimate price',
    unlockCondition: (state) => state.npcs.player.sacrificedSelf
  },
  
  'ending_escape': {
    name: 'The Escape',
    description: 'You fled, leaving them to their fate',
    difficulty: 'easy',
    hint: 'Sometimes survival means running',
    unlockCondition: (state) => state.npcs.player.fled
  },
  
  'ending_betrayal': {
    name: 'The Betrayal',
    description: 'You chose yourself over all others',
    difficulty: 'medium',
    hint: 'Trust is a weapon in the right hands',
    unlockCondition: (state) => state.npcs.player.betrayedAll
  },
  
  'ending_ascension': {
    name: 'The Ascension',
    description: 'You became one with the well',
    difficulty: 'hard',
    hint: 'The well offers power to those who listen',
    unlockCondition: (state) => state.npcs.player.acceptedWellPower
  },
  
  'ending_damnation': {
    name: 'The Damnation',
    description: 'Your sins caught up with you',
    difficulty: 'medium',
    hint: 'Betrayal has consequences',
    unlockCondition: (state) => state.npcs.player.wasBetrayed && state.npcs.player.betrayedOthers
  },
  
  'ending_redemption': {
    name: 'The Redemption',
    description: 'You broke the cycle of mistrust',
    difficulty: 'hard',
    hint: 'Forgiveness is harder than vengeance',
    unlockCondition: (state) => state.npcs.all.every(n => n.forgiven)
  },
  
  'ending_cycle': {
    name: 'The Cycle',
    description: 'Nothing changed. Nothing ever does.',
    difficulty: 'easy',
    hint: 'Some patterns repeat endlessly',
    unlockCondition: (state) => state.npcs.player.maintainedStatusQuo
  },
  
  'ending_truth': {
    name: 'The Truth',
    description: 'You discovered what really happened',
    difficulty: 'hard',
    hint: 'Question everything, especially yourself',
    unlockCondition: (state) => state.npcs.player.discoveredTrueVillain
  },
  
  // Secret endings
  'ending_developer': {
    name: 'Behind the Curtain',
    description: 'You found the creators',
    difficulty: 'secret',
    hint: 'Look beyond the story',
    unlockCondition: (meta) => meta.secretFlags.foundDevelopersNote,
    hidden: true
  },
  
  'ending_true': {
    name: 'The True Ending',
    description: 'You understood everything',
    difficulty: 'secret',
    hint: 'See all, know all, then choose',
    unlockCondition: (meta) => meta.endingsDiscovered.length >= 8,
    hidden: true
  }
};

6.2 Secret Interactions

const SECRET_INTERACTIONS = {
  'secret_well_whispers': {
    name: 'Listener',
    description: 'Heard the well speak',
    condition: (meta) => meta.secretFlags.heardWellSpeak,
    hint: 'Listen when the water runs low'
  },
  
  'secret_true_name': {
    name: 'Namer',
    description: 'Discovered the true name',
    condition: (meta) => meta.secretFlags.discoveredTrueName,
    hint: 'Names have power. Find the right one.'
  },
  
  'secret_all_villains': {
    name: 'Know Thy Enemy',
    description: 'Experienced all villain rotations',
    condition: (meta) => {
      const villainsSeen = new Set(
        meta.previousPlaythroughs.map(p => p.villain)
      );
      return villainsSeen.size >= 6;
    },
    hint: 'Evil wears many faces'
  },
  
  'secret_speed_run': {
    name: 'Rushed',
    description: 'Completed in under 30 minutes',
    condition: (meta, session) => session.sessionPlaytime < 1800,
    hint: 'Time is always running'
  },
  
  'secret_completionist': {
    name: 'Completionist',
    description: 'Discovered all endings',
    condition: (meta) => meta.endingsDiscovered.length >= 10,
    hint: 'Every path leads somewhere'
  },
  
  'secret_patient': {
    name: 'Patient Zero',
    description: 'Played 20+ times',
    condition: (meta) => meta.playSessionCount >= 20,
    hint: 'Persistence is its own reward'
  },
  
  'secret_ghost_mode': {
    name: 'Among the Ghosts',
    description: 'Enabled ghost mode',
    condition: (meta) => meta.secretFlags.unlockedGhostMode,
    hint: 'You are not alone'
  },
  
  'secret_no_save': {
    name: 'Pure',
    description: 'Completed without closing the tab',
    condition: (session) => session.noTabClose,
    hint: 'Commit to your choices'
  }
};

6.3 Progress Display

const PROGRESS_DISPLAY = {
  // Endings progress
  renderEndingsProgress: (meta) => {
    const discovered = meta.endingsDiscovered;
    const total = Object.keys(ENDINGS).length;
    const percentage = (discovered.length / total) * 100;
    
    return {
      discovered: discovered.length,
      total: total,
      percentage: Math.round(percentage),
      discoveredList: discovered.map(e => ({
        id: e,
        name: ENDINGS[e].name,
        description: ENDINGS[e].description
      })),
      lockedCount: total - discovered.length,
      hints: discovered.length < total ? this.getHints(discovered) : []
    };
  },
  
  // Secrets progress
  renderSecretsProgress: (meta) => {
    const secrets = Object.values(SECRET_INTERACTIONS);
    const unlocked = secrets.filter(s => s.condition(meta));
    
    return {
      unlocked: unlocked.length,
      total: secrets.length,
      unlockedList: unlocked.map(s => ({
        name: s.name,
        description: s.description
      })),
      lockedHints: secrets
        .filter(s => !unlocked.includes(s))
        .map(s => s.hint)
    };
  }
};

6.4 Speed Run Recognition

const SPEED_RUN_SYSTEM = {
  // Categories
  categories: {
    'any_ending': {
      name: 'Any Ending',
      description: 'Reach any ending as fast as possible'
    },
    'true_ending': {
      name: 'True Ending',
      description: 'Reach the true ending as fast as possible'
    },
    'all_endings': {
      name: 'All Endings',
      description: 'See every ending (tracked across sessions)'
    }
  },
  
  // Recognition thresholds
  thresholds: {
    'any_ending': {
      gold: 900,    // 15 minutes
      silver: 1800, // 30 minutes
      bronze: 3600  // 1 hour
    },
    'true_ending': {
      gold: 3600,   // 1 hour
      silver: 7200, // 2 hours
      bronze: 10800 // 3 hours
    }
  },
  
  // Display
  renderSpeedBadge: (time, category) => {
    const thresholds = SPEED_RUN_SYSTEM.thresholds[category];
    
    if (time <= thresholds.gold) {
      return { medal: '🥇', label: 'Gold', class: 'speed-gold' };
    } else if (time <= thresholds.silver) {
      return { medal: '🥈', label: 'Silver', class: 'speed-silver' };
    } else if (time <= thresholds.bronze) {
      return { medal: '🥉', label: 'Bronze', class: 'speed-bronze' };
    }
    
    return null;
  }
};

6.5 Post-Credits Rewards

const POST_CREDITS = {
  // Based on ending achieved
  endingRewards: {
    'ending_sacrifice': {
      unlock: 'bonus_dialogue_sacrifice_wisdom',
      content: 'The Elder speaks of those who gave everything...'
    },
    'ending_true': {
      unlock: 'developer_commentary',
      content: 'The creators share their intentions...'
    }
  },
  
  // Based on play history
  historyRewards: (meta) => {
    const rewards = [];
    
    if (meta.playSessionCount >= 5) {
      rewards.push({
        unlock: 'pattern_recognition_hint',
        content: 'You\'ve seen enough to recognize patterns...'
      });
    }
    
    if (meta.endingsDiscovered.length >= 4) {
      rewards.push({
        unlock: 'villain_prediction',
        content: 'You can sense who the true villain might be...'
      });
    }
    
    return rewards;
  },
  
  // Personalized message
  personalizedMessage: (meta, session) => {
    const patterns = analyzePlayPatterns(meta);
    
    return {
      title: `Player ${meta.deviceId.slice(-6)}`,
      message: generatePersonalMessage(patterns),
      stats: {
        totalTime: formatTime(meta.totalPlaytime),
        sessions: meta.playSessionCount,
        endings: meta.endingsDiscovered.length,
        tendency: meta.playerTendencies.preferredApproach
      }
    };
  }
};

7. META-PROGRESSION

7.1 What Carries Over

const CARRYOVER_RULES = {
  // Always preserved
  persistent: [
    'endingsDiscovered',
    'secretFlags',
    'totalPlaytime',
    'playSessionCount',
    'firstPlayDate',
    'playerTendencies',
    'previousPlaythroughs'
  ],
  
  // Preserved with conditions
  conditional: {
    'branchAccess': {
      preserve: true,
      resetEachSession: false
    },
    'npcMemory': {
      preserve: true,
      // Reset specific memories each session
      reset: ['immediate_betrayals', 'session_specific_trust']
    }
  },
  
  // Never preserved (fresh each play)
  ephemeral: [
    'currentNodeId',
    'choiceHistory',
    'narrativeFlags',
    'well.waterLevel',
    'npcs.trustScore'
  ]
};

7.2 Player Tendency Analysis

class TendencyAnalyzer {
  constructor(meta) {
    this.tendencies = meta.playerTendencies;
    this.history = meta.previousPlaythroughs;
  }
  
  analyze() {
    return {
      approach: this.determineApproach(),
      riskTolerance: this.calculateRiskTolerance(),
      trustPattern: this.analyzeTrustPattern(),
      exploration: this.calculateExploration(),
      completionism: this.detectCompletionism(),
      speedPreference: this.detectSpeedPreference()
    };
  }
  
  determineApproach() {
    const { trustChoices, betrayChoices, neutralChoices } = this.tendencies;
    const total = this.tendencies.totalChoices;
    
    const trustRatio = trustChoices / total;
    const betrayRatio = betrayChoices / total;
    
    if (trustRatio > 0.6) return 'trusting';
    if (betrayRatio > 0.4) return 'calculated';
    if (betrayRatio > 0.6) return 'manipulative';
    if (Math.abs(trustRatio - betrayRatio) < 0.1) return 'balanced';
    
    return 'cautious';
  }
  
  calculateRiskTolerance() {
    // Based on choices made at low water levels
    const riskyChoices = this.history.flatMap(h => 
      h.keyChoices.filter(c => CHOICE_RISK_LEVEL[c] === 'high')
    );
    
    return Math.min(riskyChoices.length / 10, 1.0);
  }
  
  analyzeTrustPattern() {
    // Do they trust the same NPCs consistently?
    const trustPatterns = this.history.map(h => h.trustedNPC);
    const consistency = this.calculateConsistency(trustPatterns);
    
    return {
      consistent: consistency > 0.7,
      preferredNPC: this.mostFrequent(trustPatterns),
      consistencyScore: consistency
    };
  }
  
  calculateExploration() {
    const uniqueNodes = new Set(
      this.history.flatMap(h => h.visitedNodes)
    );
    const totalPossibleNodes = 150; // Estimated total
    
    return uniqueNodes.size / totalPossibleNodes;
  }
  
  detectCompletionism() {
    const endings = this.tendencies.endingsDiscovered?.length || 0;
    const secrets = Object.values(this.tendencies.secretFlags || {}).filter(Boolean).length;
    
    return endings >= 5 || secrets >= 5;
  }
}

7.3 Game “Learning” System

class GameLearningSystem {
  constructor(meta) {
    this.meta = meta;
    this.analyzer = new TendencyAnalyzer(meta);
  }
  
  // Adapt game based on player patterns
  adaptGame() {
    const tendencies = this.analyzer.analyze();
    
    return {
      // Adjust difficulty subtly
      difficultyModifier: this.calculateDifficulty(tendencies),
      
      // Personalize NPC behavior
      npcPersonalities: this.personalizeNPCs(tendencies),
      
      // Unlock relevant hints
      hintSystem: this.configureHints(tendencies),
      
      // Customize ghost mode
      ghostDisplay: this.configureGhostMode(tendencies)
    };
  }
  
  personalizeNPCs(tendencies) {
    const personalization = {};
    
    // If player is consistently trusting, add betrayal opportunities
    if (tendencies.approach === 'trusting') {
      personalization.trustOpportunities = 'increased';
      personalization.betrayalConsequences = 'amplified';
    }
    
    // If player is manipulative, NPCs become more guarded
    if (tendencies.approach === 'manipulative') {
      personalization.initialTrust = 'reduced';
      personalization.npcSuspicion = 'increased';
    }
    
    // If completionist, add more secrets
    if (tendencies.completionism) {
      personalization.secretDensity = 'high';
      personalization.easterEggs = 'enabled';
    }
    
    return personalization;
  }
  
  configureHints(tendencies) {
    // Don't spoil, but guide based on patterns
    if (tendencies.exploration < 0.3) {
      return { hintFrequency: 'increased', type: 'exploration' };
    }
    
    if (tendencies.riskTolerance < 0.3) {
      return { hintFrequency: 'subtle', type: 'risk_assessment' };
    }
    
    return { hintFrequency: 'normal', type: 'general' };
  }
}

7.4 Personalized Post-Credits

const PERSONALIZED_ENDINGS = {
  generate: (meta, session) => {
    const analyzer = new TendencyAnalyzer(meta);
    const tendencies = analyzer.analyze();
    
    return {
      // Opening
      opening: generateOpening(tendencies, meta),
      
      // Statistics
      stats: generatePersonalStats(meta, session),
      
      // Pattern recognition
      patterns: generatePatternInsight(tendencies),
      
      // Next play suggestions
      suggestions: generateSuggestions(meta, tendencies),
      
      // Special message if applicable
      special: generateSpecialMessage(meta)
    };
  }
};
 
function generateOpening(tendencies, meta) {
  const openings = {
    trusting: "You trust easily. The world needs more like you... or perhaps fewer.",
    calculated: "Every choice weighed, every outcome considered. But did you enjoy the journey?",
    manipulative: "You played them all like pieces on a board. Did anyone win?",
    balanced: "You walked the middle path. Sometimes wisdom, sometimes indecision.",
    cautious: "You hesitated at every turn. The well doesn't wait for certainty."
  };
  
  return openings[tendencies.approach] || openings.balanced;
}
 
function generatePersonalStats(meta, session) {
  return {
    sessionTime: formatTime(session.sessionPlaytime),
    totalTime: formatTime(meta.totalPlaytime),
    thisEnding: session.endingId,
    endingsSeen: `${meta.endingsDiscovered.length}/10`,
    completion: `${Math.round((meta.endingsDiscovered.length / 10) * 100)}%`,
    playStyle: meta.playerTendencies.preferredApproach
  };
}
 
function generateSuggestions(meta, tendencies) {
  const suggestions = [];
  
  if (meta.endingsDiscovered.length < 3) {
    suggestions.push("Try trusting someone you didn't before.");
  }
  
  if (!meta.secretFlags.discoveredTrueName) {
    suggestions.push("Listen for names. They hold power.");
  }
  
  if (tendencies.approach === 'trusting') {
    suggestions.push("Consider: not everyone deserves your trust.");
  }
  
  if (tendencies.approach === 'manipulative') {
    suggestions.push("What would happen if you were truly honest, just once?");
  }
  
  if (meta.playSessionCount >= 5) {
    suggestions.push("You've seen enough. Look for what's hidden in plain sight.");
  }
  
  return suggestions;
}
 
function generateSpecialMessage(meta) {
  if (meta.playSessionCount >= 20) {
    return "You've become part of this place. We made this for you. Thank you for staying.";
  }
  
  if (meta.endingsDiscovered.length >= 8) {
    return "You understand now. But understanding is not the same as acceptance.";
  }
  
  return null;
}

8. IMPLEMENTATION CHECKLIST

Core Systems

  • Save state schema implementation
  • Villain rotation algorithm
  • Random event system
  • Ghost mode data aggregation
  • Well drain mechanics
  • New Game+ unlock system
  • Achievement tracking
  • Tendency analysis

UI Components

  • Well indicator display
  • Ghost mode toggle
  • Progress dashboard
  • Post-credits screen
  • Choice time cost display

Data Pipeline

  • Anonymous telemetry collection
  • Aggregate statistics computation
  • Privacy-compliant storage
  • Real-time ghost data sync

Testing

  • Villain distribution balance
  • Well drain pacing
  • Ghost mode accuracy
  • Save migration paths
  • Cross-session memory

Document Version: 1.0 Last Updated: 2024 For: Moloch Interactive Experience