const MULTIPLIERS = { "Rainbow": 50, "Golden": 20, "Frozen": 10, "Bloodlit": 4, "Pollinated": 3, "Wet": 2, "Chilled": 2 }; const BASELINE_GOLDEN_CHANCE = 0.01; // 1.0% class Decision { /** * @param {Object} crop - { species, baseValue, scale, mutations: [] } * @param {Object} worldEvents - { * Time_Until_Next_Event_Hrs, * Time_Assuming_Growth_Speed_Boost, * Time_Remaining_Hrs, * P_Next_Rain_Thunderstorm, * P_Next_Frost, * Time_To_Next_Blood_Moon_Hrs, * Event_Active * } */ static shouldHarvest(crop, worldEvents) { // Step 3.1: Deterministic Value (V_HarvestNow) const vHarvestNow = this.calculateDeterministicValue(crop); // Step 3.2: Growth Progression & Time Compression // Delta T effective // If Event_Active is true, speed is 1.5x, else 1.0x const speedMultiplier = worldEvents.Event_Active ? 1.5 : 1.0; const deltaTEff = worldEvents.Time_Until_Next_Event_Hrs * speedMultiplier; const tRemNext = worldEvents.Time_Remaining_Hrs - deltaTEff; // Critical Check: If crop matures during wait, harvest NOW (continuing value is capped/complicated) // Ignoring complicated capped value logic for now as per instructions "E is capped" -> imply we might just harvest? // Actually instructions say: "If T_rem <= 0 ... calculation focuses only on expected mutation gains over Delta t" // But usually if it matures, we might as well harvest or we risk rotting? // For this V1 implementation, if it matures, let's assume we proceed with E calculation but T_rem is 0. // However, the prompt says "If T_rem > 0, the harvest is delayed". Implicitly if <=0, maybe we consider it 'mature' state. // Let's stick to the prompt's implied logic for now: calculate E regardless, but note the state. // Step 3.3: Stochastic Transition Value (E) const E = this.calculateStochasticTransition(crop, vHarvestNow, worldEvents); // Step 3.4: Strategic Interval Check (Blood Moon) let maxE = E; let strategy = "Standard Wait"; // Check if we can wait for Blood Moon // Use simplified logic: if generic wait leaves us with time, AND we have enough time for BM // Note: T_rem(t + Delta_t) > 0 check from prompt if (tRemNext > 0 && worldEvents.Time_To_Next_Blood_Moon_Hrs <= worldEvents.Time_Remaining_Hrs) { const eBM = this.calculateBloodMoonValue(vHarvestNow); if (eBM > E) { maxE = eBM; strategy = "Blood Moon Wait"; } } // Step 3.5: Optimal Decision Synthesis const decision = { action: vHarvestNow >= maxE ? "HARVEST_NOW" : "WAIT", vHarvestNow: vHarvestNow, expectedValue: maxE, strategy: strategy, details: { E_Standard: E, tRemNext: tRemNext } }; return decision; } static calculateDeterministicValue(crop) { let multiplier = 1.0; if (crop.mutations) { crop.mutations.forEach(m => { const val = MULTIPLIERS[m] || 1.0; // Default to 1 if unknown or wait? Assume multiplicative multiplier *= val; }); } // Base Value * Mass * Multipliers // Mass is 'scale' return (crop.baseValue || 1) * (crop.scale || 1) * multiplier; } static calculateStochasticTransition(crop, vNow, worldEvents) { const mutations = new Set(crop.mutations || []); // A. Rain/Thunderstorm // Base Gain: 50% chance of Wet (2x) // Fusion: If Chilled present, Wet can fuse to Frozen (10x). Chilled (2x) is lost. // Fusion Logic: "Rain ... has a chance to fuse it" - let's assume 100% chance IF Rain happens for this calculation? // Prompt says: "If Chilled is present, Rain... has a chance". Let's assume the event IS Rain. // Wait, structure is: E = P_Rain * V_Rain + ... // Inside V_Rain: What is the gain? // Prompt: "1. Calculate expected fusion gain factor". // Assumption: If 'Chilled' exists, 2x becomes 10x? Or is it 50% chance? // Prompt says "Rain... has a chance". Usually fusion is guaranteed if conditions met in these games? // Let's assume for V1: If Rain hits, and Chilled exists => Frozen (10x). // If Chilled NOT exists => Wet (2x) with 50% chance. let multiplierGainA = 1.0; if (mutations.has("Chilled")) { // Fusion: Chilled (2x) -> Frozen (10x). Gain factor = 10 / 2 = 5x? // Wait, vNow already includes Chilled(2x). // New Multiplier Product would be: (Old_Prod / 2) * 10 = Old_Prod * 5. // So Gain Factor is 5. // Probability of fusion? "has a chance". Let's assume 100% GIVEN Rain for now, or 50%? // "Base Gain: 50% chance of 2x Wet". // Let's assume Fusion is also 50% chance if Chilled is there? Or maybe the event IS the fusion? // Let's use 50% for fusion chance given Rain. multiplierGainA = 1.0 + 0.5 * (5.0 - 1.0); // Exp Value of factor? // Actually, let's look at value: // avg_mult = 0.5 * 5 (Fusion) + 0.5 * 1 (Nothing) = 3.0 relative to current? // No, if nothing happens, it stays 1.0. // So E[Factor] = 0.5 * 5 + 0.5 * 1 = 3.0? } else { // Wet (2x). Gain from 1.0 to 2.0. // If we already have Wet? Usually mutations are unique? // Prompt says "Identify all current, UNIQUE mutations". // If we have Wet, can we get double Wet? Assume No. if (!mutations.has("Wet") && !mutations.has("Frozen")) { // Assuming Frozen prevents Wet? multiplierGainA = 1.0 + 0.5 * (2.0 - 1.0); // 0.5 * 2 + 0.5 * 1 = 1.5 } } const V_A = vNow * multiplierGainA; // B. Frost // Base Gain: 50% chance of Chilled (2x). // Fusion: If Wet present -> Frozen (10x). Replaces Wet (2x). let multiplierGainB = 1.0; if (mutations.has("Wet")) { // Fusion: Wet(2x) -> Frozen(10x). Factor = 5. // Chance: 50%? multiplierGainB = 1.0 + 0.5 * (5.0 - 1.0); // 3.0 } else { if (!mutations.has("Chilled") && !mutations.has("Frozen")) { multiplierGainB = 1.0 + 0.5 * (2.0 - 1.0); // 1.5 } } const V_B = vNow * multiplierGainB; // C. Other Events (Baseline) // 1.0% chance of Golden (20x). // Gain factor = 20. // V_C = vNow * (1 + P_Golden * 20) ??? // Wait, "V_HarvestNow * (1 + P_Golden * 20)" -> This implies P_Golden is small, // e.g. 0.01. So val = vNow * (1 + 0.2) = 1.2 * vNow? // "Calculate the expected baseline gain V_C" // Prompt formula: V_C = V_HarvestNow * (1 + P_Golden * 20) // Is it additive? Gain = 20x value? Or is specific mutation 20x? // If I have 1x multiplier. Get Golden -> 20x. Gain is +19x? // Factor = 20. // Expected Factor = (1 - P) * 1 + P * 20 = 1 - P + 20P = 1 + 19P. // Prompt says "1 + P_Golden * 20". This is approx 1 + 20P. Close enough. // Let's use Prompt Formula EXACTLY. const V_C = vNow * (1 + BASELINE_GOLDEN_CHANCE * 20); // 1 + 0.01*20 = 1.2 // Total E let P_A = worldEvents.P_Next_Rain_Thunderstorm; let P_B = worldEvents.P_Next_Frost; // CRITICAL CHECK: If crop matures before the event, we can't get the event benefit // worldEvents.Time_Until_Next_Event_Hrs is the wait time. // worldEvents.Time_Remaining_Hrs is how much growth is left. // If Wait > Remaining, we mature before event. // Note: Speed multiplier is already applied to Calculate tRemNext, but that's what remains AFTER wait. // If tRemNext < 0, it means we matured DURING the wait. // If we mature during the wait, do we get the event? // The event starts AT the end of the wait. So if we mature BEFORE the end of wait, we miss it. const timeToEvent = worldEvents.Time_Until_Next_Event_Hrs; // Real hours // Need to check if we mature before this real time passed? // Crop matures in Time_Remaining_Hrs (assuming standard speed? or current speed?) // If current speed is 1x. // If speed is 1.5x (Event Active), Time_Remaining_Hrs is still "hours at 1x"? // Usually Time_Remaining is "real time remaining"? // Let's assume Time_Remaining_Hrs is "Time until maturity in real hours at current speed" provided by caller? // Or is it "Mass needed"? // Prompt Check 3.2: "Time Remaining in the next state: T_rem(t+dt) = Time_Remaining_Hrs - Delta_T_eff" // Delta_T_eff = Time_Until_Next_Event * Speed. // This implies Time_Remaining_Hrs is in "Growth Units" (Hours at 1x speed). // If T_rem(t+dt) <= 0, we mature *during* or *before* the event. // Since the event triggers *at* t+dt, we are already mature. // Thus, we likely miss the specific "Event Mutation" which usually requires growing *during* the event (or the event affecting the plant). // If the event is "Rain starts now", and we are already mature, does Rain affect us? // Usually mutations happen *while growing*. // So if tRemNext <= 0, we set P_A and P_B to 0. // Also, from user prompt: "If T_rem(t+dt) <= 0 ... calculation focuses only on expected mutation gains over Delta t" // This confirms we exclude the specific Event outcome that happens *at* the transition. const deltaTEff = worldEvents.Time_Until_Next_Event_Hrs * (worldEvents.Event_Active ? 1.5 : 1.0); const nextStateRem = worldEvents.Time_Remaining_Hrs - deltaTEff; if (nextStateRem <= 0) { P_A = 0; P_B = 0; // P_C absorbs the probability? Or do we just lose it? // "The expected value of waiting, E, is the sum... for the three possible random event outcomes" // If Rain is impossible, does P_Rain become "Nothing"? // Yes, prob of Rain event occuring is real, but effect is null. // So V_A becomes V_Now? Or just exclude from sum? // Since probabilities must sum to 1? // Actually, P_Next_Rain is prob of *Event*. The Event happens regardless of our crop. // But our *Gain* is 0. // So V_A = V_Now. V_B = V_Now. // Effectively, we just take the Baseline gain. } const P_C = 1.0 - P_A - P_B; // If we can't benefit from events, V_A and V_B should fallback to V_Now? // Or V_C? // If Event happens (Rain), but we are mature -> No effect -> Value = V_Now. const final_V_A = (nextStateRem <= 0) ? vNow : V_A; const final_V_B = (nextStateRem <= 0) ? vNow : V_B; // Baseline gain applies over Delta t? // "1.0% random chance per growth cycle (Delta t)" // If we only grow for part of Delta t? // Let's assume Baseline applies fully for now, or maybe only if we are growing? // If we stop growing halfway, chance is lower. // But let's stick to simple: V_C applies. return (P_A * final_V_A) + (P_B * final_V_B) + (P_C * V_C); } static calculateBloodMoonValue(vNow) { // 33% chance of 4x Bloodlit. // Note: Bloodlit is 4x. // Formula from prompt: vNow * (1 + 0.33 * 4x) // "4x" probably means the number 4. return vNow * (1 + 0.33 * 4); } } // Browser / Node compatibility if (typeof window !== 'undefined') { window.MagicBot = window.MagicBot || {}; window.MagicBot.Decision = Decision; } if (typeof module !== 'undefined') { module.exports = Decision; }