Files
Magic-Garden-Bot/extension/modules/decision.js
2025-12-09 23:21:09 +00:00

253 lines
12 KiB
JavaScript

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;
}