diff --git a/Pages/Garden.php b/Pages/Garden.php
index 543b7f6..1bb4d18 100644
--- a/Pages/Garden.php
+++ b/Pages/Garden.php
@@ -294,6 +294,7 @@ $PageSection = "Garden";
+
diff --git a/Scripts/Garden/Garden.js b/Scripts/Garden/Garden.js
index a24927a..b1d386d 100644
--- a/Scripts/Garden/Garden.js
+++ b/Scripts/Garden/Garden.js
@@ -65,19 +65,23 @@ async function StartGame() {
GameSave = await LoadGame();
+ const InitialSimulationResult =
+ AdvanceGameSimulation(
+ GameSave,
+ Date.now()
+ );
+
BindGardenTools();
BindGardenSelector();
BindQuickBuyControls();
EnsureSelectedSeed();
- const InitialMutationResult =
- CheckAllGardenMutations(
- GameSave
- );
-
RenderGame();
- if (InitialMutationResult.Attempted) {
+ if (
+ InitialSimulationResult.Advanced ||
+ InitialSimulationResult.Dirty
+ ) {
await SaveGame(
GameSave
);
@@ -184,6 +188,13 @@ function BindGardenSelector() {
async function SwitchGarden(
Direction
) {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
const GardenCount =
GameSave.Gardens.length;
@@ -219,7 +230,8 @@ async function SwitchGarden(
const MutationResult =
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -348,49 +360,15 @@ function RenderGardenSelector() {
function CheckAllGardenMutations(
- SaveData
+ SaveData,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
- const OriginalGardenIndex =
- SaveData.ActiveGardenIndex;
-
- const Result = {
- Attempted: false,
- Changed: false
- };
-
-
- for (
- let GardenIndex = 0;
- GardenIndex <
- SaveData.Gardens.length;
- GardenIndex++
- ) {
- SetActiveGardenIndex(
- SaveData,
- GardenIndex
- );
-
- const GardenResult =
- CheckGardenMutations(
- SaveData
- );
-
- Result.Attempted =
- Result.Attempted ||
- GardenResult.Attempted;
-
- Result.Changed =
- Result.Changed ||
- GardenResult.Changed;
- }
-
-
- SetActiveGardenIndex(
+ return CheckAllGardenMutationsAtTime(
SaveData,
- OriginalGardenIndex
+ AtTime
);
-
- return Result;
}
@@ -1832,7 +1810,9 @@ function CreateGardenPlotTimer(
0,
Plant.GrowthTime -
(
- Date.now() -
+ GetSimulationTime(
+ GameSave
+ ) -
Plot.PlantedAt
)
);
@@ -1905,6 +1885,13 @@ function FormatGardenRemainingTime(
async function PlantSeed(
PlotIndex
) {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
if (
GameSave.Garden.Plots[
PlotIndex
@@ -1959,7 +1946,7 @@ async function PlantSeed(
PlotIndex
] = {
Plant: PlantKey,
- PlantedAt: Date.now()
+ PlantedAt: ActionTime
};
@@ -1970,7 +1957,8 @@ async function PlantSeed(
);
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -1982,6 +1970,13 @@ async function PlantSeed(
async function PlantManySeeds() {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
EnsureSelectedSeed();
if (SelectedSeedId === null) {
@@ -2066,7 +2061,7 @@ async function PlantManySeeds() {
}
- const PlantedAt = Date.now();
+ const PlantedAt = ActionTime;
for (
let Index = 0;
@@ -2093,7 +2088,8 @@ async function PlantManySeeds() {
);
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -2107,6 +2103,13 @@ async function PlantManySeeds() {
async function HarvestPlant(
PlotIndex
) {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
const Plot =
GameSave.Garden.Plots[
PlotIndex
@@ -2166,7 +2169,8 @@ async function HarvestPlant(
);
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -2180,6 +2184,13 @@ async function HarvestPlant(
async function RemovePlant(
PlotIndex
) {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
const Plot =
GameSave.Garden.Plots[
PlotIndex
@@ -2257,7 +2268,8 @@ async function RemovePlant(
);
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -2271,6 +2283,13 @@ async function RemovePlant(
async function FertilizePlant(
PlotIndex
) {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
const Plot =
GameSave.Garden.Plots[
PlotIndex
@@ -2330,7 +2349,8 @@ async function FertilizePlant(
);
CheckGardenMutations(
- GameSave
+ GameSave,
+ ActionTime
);
RenderGame();
@@ -2342,6 +2362,13 @@ async function FertilizePlant(
async function UseFutureSight() {
+ const ActionTime = Date.now();
+
+ AdvanceGameSimulation(
+ GameSave,
+ ActionTime
+ );
+
if (
!IsGardenToolUnlocked(
GameSave,
@@ -2450,41 +2477,37 @@ function CreateFutureSightPreview() {
async function GardenTick() {
- RenderGarden();
- RenderTools();
- RenderNextHarvest();
- RenderGardenOverview();
- RenderGardenEconomy();
- RenderGardenInfoLayout();
-
-
- if (
- SelectedGardenTool ===
- "FutureSight" &&
- FutureSightPreview !== null
- ) {
- return;
- }
-
-
if (MutationCheckPending) {
return;
}
-
MutationCheckPending = true;
-
try {
- const MutationResult =
- CheckAllGardenMutations(
- GameSave
+ const SimulationResult =
+ AdvanceGameSimulation(
+ GameSave,
+ Date.now()
);
+ if (
+ SimulationResult.Attempted &&
+ FutureSightPreview !== null
+ ) {
+ FutureSightPreview = null;
- if (MutationResult.Attempted) {
- RenderGame();
+ if (
+ SelectedGardenTool ===
+ "FutureSight"
+ ) {
+ SelectedGardenTool =
+ "Trowel";
+ }
+ }
+ RenderGame();
+
+ if (SimulationResult.Dirty) {
await SaveGame(
GameSave
);
@@ -2531,7 +2554,9 @@ function GetPlantGrowthProgress(
}
const Age =
- Date.now() -
+ GetSimulationTime(
+ GameSave
+ ) -
Plot.PlantedAt;
return Math.max(
@@ -2733,7 +2758,9 @@ function RenderNextHarvest() {
const Remaining =
Harvest.Time -
- Date.now();
+ GetSimulationTime(
+ GameSave
+ );
const IncludePlantName =
HasOverview;
diff --git a/Scripts/Garden/MutationEngine.js b/Scripts/Garden/MutationEngine.js
index 47d65b2..aa8ef8c 100644
--- a/Scripts/Garden/MutationEngine.js
+++ b/Scripts/Garden/MutationEngine.js
@@ -2,7 +2,10 @@ const MaxMutationPasses = 100;
function CheckGardenMutations(
- SaveData
+ SaveData,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
SaveData.MutationCooldowns ??= {};
@@ -23,13 +26,15 @@ function CheckGardenMutations(
) {
const Candidates =
FindGardenMutationCandidates(
- SaveData
+ SaveData,
+ AtTime
)
.filter(
Candidate =>
!IsMutationCandidateOnCooldown(
SaveData,
- Candidate
+ Candidate,
+ AtTime
)
)
.sort(
@@ -98,7 +103,8 @@ function CheckGardenMutations(
if (
!DoesMutationCandidateStillMatch(
SaveData,
- Candidate
+ Candidate,
+ AtTime
)
) {
continue;
@@ -108,7 +114,8 @@ function CheckGardenMutations(
if (
IsMutationCandidateOnCooldown(
SaveData,
- Candidate
+ Candidate,
+ AtTime
)
) {
continue;
@@ -119,7 +126,8 @@ function CheckGardenMutations(
StartMutationCandidateCooldown(
SaveData,
- Candidate
+ Candidate,
+ AtTime
);
@@ -136,7 +144,9 @@ function CheckGardenMutations(
const Succeeded =
- Math.random() < Chance;
+ GetSimulationRandom(
+ SaveData
+ ) < Chance;
if (Succeeded) {
@@ -151,7 +161,8 @@ function CheckGardenMutations(
ApplyMutationResult(
SaveData,
Candidate,
- Candidate.Success
+ Candidate.Success,
+ AtTime
);
@@ -176,7 +187,8 @@ function CheckGardenMutations(
const Changed =
ApplyMutationFailure(
SaveData,
- Candidate
+ Candidate,
+ AtTime
);
@@ -226,7 +238,10 @@ function CheckGardenMutations(
function FindGardenMutationCandidates(
- SaveData
+ SaveData,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
const Candidates = [];
@@ -283,7 +298,8 @@ function FindGardenMutationCandidates(
Mutation,
Orientation,
OriginX,
- OriginY
+ OriginY,
+ AtTime
);
@@ -307,7 +323,10 @@ function CreateMutationCandidate(
Mutation,
Orientation,
OriginX,
- OriginY
+ OriginY,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
const Captures = {};
const Cells = [];
@@ -357,7 +376,8 @@ function CreateMutationCandidate(
Plot,
Matcher,
Mutation,
- Captures
+ Captures,
+ AtTime
)
) {
return null;
@@ -399,7 +419,10 @@ function CreateMutationCandidate(
function DoesMutationCandidateStillMatch(
SaveData,
- Candidate
+ Candidate,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
const RefreshedCandidate =
CreateMutationCandidate(
@@ -416,7 +439,8 @@ function DoesMutationCandidateStillMatch(
Candidate.Rotation
},
Candidate.OriginX,
- Candidate.OriginY
+ Candidate.OriginY,
+ AtTime
);
@@ -437,7 +461,8 @@ function DoesPlotMatchMutationMatcher(
Plot,
Matcher,
Mutation,
- Captures
+ Captures,
+ AtTime
) {
if (
Matcher === null ||
@@ -470,7 +495,8 @@ function DoesPlotMatchMutationMatcher(
true &&
!IsMutationPlantMature(
Plot,
- Plant
+ Plant,
+ AtTime
)
) {
return false;
@@ -566,7 +592,8 @@ function DoesPlotMatchMutationMatcher(
function IsMutationPlantMature(
Plot,
- Plant
+ Plant,
+ AtTime
) {
if (
Number(
@@ -578,7 +605,7 @@ function IsMutationPlantMature(
return (
- Date.now() -
+ AtTime -
Number(
Plot.PlantedAt ?? 0
) >=
@@ -589,7 +616,10 @@ function IsMutationPlantMature(
function ApplyMutationFailure(
SaveData,
- Candidate
+ Candidate,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
const Failure =
Candidate.Mutation.Failure ??
@@ -631,7 +661,8 @@ function ApplyMutationFailure(
return ApplyMutationResult(
SaveData,
Candidate,
- Failure
+ Failure,
+ AtTime
);
}
@@ -643,7 +674,10 @@ function ApplyMutationFailure(
function ApplyMutationResult(
SaveData,
Candidate,
- ResultMatrix
+ ResultMatrix,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
if (!Array.isArray(ResultMatrix)) {
return false;
@@ -706,7 +740,8 @@ function ApplyMutationResult(
const NewPlot =
CreateMutationResultPlot(
ResultValue,
- Candidate.Captures
+ Candidate.Captures,
+ AtTime
);
@@ -749,7 +784,8 @@ function ApplyMutationResult(
function CreateMutationResultPlot(
ResultValue,
- Captures
+ Captures,
+ AtTime
) {
if (
typeof ResultValue ===
@@ -785,7 +821,7 @@ function CreateMutationResultPlot(
return {
Plant: ResultValue,
- PlantedAt: Date.now()
+ PlantedAt: AtTime
};
}
@@ -841,7 +877,7 @@ function CreateMutationResultPlot(
return {
Plant: ResultValue.Plant,
- PlantedAt: Date.now()
+ PlantedAt: AtTime
};
}
@@ -1006,42 +1042,56 @@ function GetMutationPatternSpecificity(
}
-function IsMutationCandidateOnCooldown(
+function GetMutationCandidateCooldownUntil(
SaveData,
Candidate
) {
- const CooldownUntil =
- Number(
- SaveData.MutationCooldowns[
- GetMutationCooldownKey(
- Candidate
- )
- ] ?? 0
- );
+ return Number(
+ SaveData.MutationCooldowns[
+ GetMutationCooldownKey(
+ Candidate
+ )
+ ] ?? 0
+ );
+}
- return CooldownUntil > Date.now();
+function IsMutationCandidateOnCooldown(
+ SaveData,
+ Candidate,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
+) {
+ return (
+ GetMutationCandidateCooldownUntil(
+ SaveData,
+ Candidate
+ ) > AtTime
+ );
}
function StartMutationCandidateCooldown(
SaveData,
- Candidate
+ Candidate,
+ AtTime = GetSimulationTime(
+ SaveData
+ )
) {
const Cooldown = Math.max(
- 0,
+ SimulationMinimumEventInterval,
Number(
Candidate.Mutation.Cooldown ??
0
- )
+ ) || 0
);
-
SaveData.MutationCooldowns[
GetMutationCooldownKey(
Candidate
)
- ] = Date.now() + Cooldown;
+ ] = AtTime + Cooldown;
}
diff --git a/Scripts/Garden/Save.js b/Scripts/Garden/Save.js
index 4777803..19e7486 100644
--- a/Scripts/Garden/Save.js
+++ b/Scripts/Garden/Save.js
@@ -15,6 +15,138 @@ const MaximumGardenNameLength = 32;
const StartingGardenWidth = 4;
const StartingGardenHeight = 4;
+const CurrentSaveVersion = 6;
+
+
+function CreateSimulationRandomState() {
+ if (
+ typeof crypto !== "undefined" &&
+ typeof crypto.getRandomValues ===
+ "function"
+ ) {
+ const Values =
+ new Uint32Array(1);
+
+ crypto.getRandomValues(
+ Values
+ );
+
+ return Values[0] >>> 0;
+ }
+
+ return (
+ Date.now() ^
+ Math.floor(
+ Math.random() *
+ 0xFFFFFFFF
+ )
+ ) >>> 0;
+}
+
+
+function GetLegacySimulationRandomState(
+ SaveData
+) {
+ const Source =
+ String(
+ SaveData.LastSavedAt ?? 0
+ ) +
+ ":" +
+ String(
+ SaveData.Revision ?? 0
+ ) +
+ ":" +
+ String(
+ SaveData.Version ?? 0
+ );
+
+ let Hash = 2166136261;
+
+ for (
+ let Index = 0;
+ Index < Source.length;
+ Index++
+ ) {
+ Hash ^= Source.charCodeAt(
+ Index
+ );
+
+ Hash = Math.imul(
+ Hash,
+ 16777619
+ );
+ }
+
+ return Hash >>> 0;
+}
+
+
+function GetSavedSimulationTime(
+ SaveData
+) {
+ const Time = Number(
+ SaveData?.Simulation
+ ?.LastProcessedAt
+ );
+
+ return Number.isFinite(Time)
+ ? Time
+ : Date.now();
+}
+
+
+function NormalizeSimulationData(
+ SaveData
+) {
+ const Now = Date.now();
+
+ if (
+ SaveData.Simulation === null ||
+ typeof SaveData.Simulation !==
+ "object" ||
+ Array.isArray(
+ SaveData.Simulation
+ )
+ ) {
+ SaveData.Simulation = {};
+ }
+
+ const LastProcessedAt = Number(
+ SaveData.Simulation
+ .LastProcessedAt ??
+ SaveData.LastSavedAt ??
+ Now
+ );
+
+ SaveData.Simulation.LastProcessedAt =
+ Math.min(
+ Now,
+ Math.max(
+ 0,
+ Math.floor(
+ Number.isFinite(
+ LastProcessedAt
+ )
+ ? LastProcessedAt
+ : Now
+ )
+ )
+ );
+
+ const RandomState = Number(
+ SaveData.Simulation.RandomState
+ );
+
+ SaveData.Simulation.RandomState =
+ Number.isInteger(RandomState) &&
+ RandomState >= 0 &&
+ RandomState <= 0xFFFFFFFF
+ ? RandomState >>> 0
+ : GetLegacySimulationRandomState(
+ SaveData
+ );
+}
+
function CreateGardenData(
Name = DefaultGardenName
@@ -392,10 +524,16 @@ function CreateNewSave() {
return NormalizeSaveData({
- Version: 5,
+ Version: CurrentSaveVersion,
Revision: 0,
LastSavedAt: Date.now(),
+ Simulation: {
+ LastProcessedAt: Date.now(),
+ RandomState:
+ CreateSimulationRandomState()
+ },
+
Currency: {
Dew: StartingDew
},
@@ -470,7 +608,7 @@ function NormalizeSaveData(
Number(
SaveData.Version ?? 1
),
- 5
+ CurrentSaveVersion
);
SaveData.Revision = Number(
@@ -482,6 +620,10 @@ function NormalizeSaveData(
Date.now()
);
+ NormalizeSimulationData(
+ SaveData
+ );
+
SaveData.Currency ??= {};
SaveData.Currency.Dew = Number(
diff --git a/Scripts/Garden/Simulation.js b/Scripts/Garden/Simulation.js
new file mode 100644
index 0000000..5f20a1e
--- /dev/null
+++ b/Scripts/Garden/Simulation.js
@@ -0,0 +1,447 @@
+const SimulationMinimumEventInterval = 1000;
+const MaximumSimulationSteps = 250000;
+
+
+function GetSimulationTime(
+ SaveData
+) {
+ return GetSavedSimulationTime(
+ SaveData
+ );
+}
+
+
+function SetSimulationTime(
+ SaveData,
+ Time
+) {
+ SaveData.Simulation ??= {};
+
+ SaveData.Simulation.LastProcessedAt =
+ Math.max(
+ 0,
+ Math.floor(
+ Number(Time) || 0
+ )
+ );
+
+ return SaveData.Simulation.LastProcessedAt;
+}
+
+
+function GetSimulationRandom(
+ SaveData
+) {
+ SaveData.Simulation ??= {};
+
+ let State = Number(
+ SaveData.Simulation.RandomState ?? 0
+ ) >>> 0;
+
+ State = (
+ State + 0x6D2B79F5
+ ) >>> 0;
+
+ SaveData.Simulation.RandomState =
+ State;
+
+ let Value = State;
+
+ Value = Math.imul(
+ Value ^ Value >>> 15,
+ Value | 1
+ );
+
+ Value ^=
+ Value +
+ Math.imul(
+ Value ^ Value >>> 7,
+ Value | 61
+ );
+
+ return (
+ (Value ^ Value >>> 14) >>> 0
+ ) / 4294967296;
+}
+
+
+function MergeSimulationMutationResult(
+ Target,
+ Source
+) {
+ Target.Attempted =
+ Target.Attempted ||
+ Source.Attempted;
+
+ Target.Changed =
+ Target.Changed ||
+ Source.Changed;
+
+ Target.SuccessfulMutations.push(
+ ...(Source.SuccessfulMutations ?? [])
+ );
+
+ Target.NewlyDiscoveredMutations.push(
+ ...(Source.NewlyDiscoveredMutations ?? [])
+ );
+
+ Target.NewlyDiscoveredPlants.push(
+ ...(Source.NewlyDiscoveredPlants ?? [])
+ );
+}
+
+
+function CreateSimulationResult(
+ StartTime,
+ TargetTime
+) {
+ return {
+ StartTime,
+ TargetTime,
+ Advanced: TargetTime > StartTime,
+ Attempted: false,
+ Changed: false,
+ Dirty: false,
+ CaughtUp: StartTime >= TargetTime,
+ EndTime: StartTime,
+ Steps: 0,
+ SuccessfulMutations: [],
+ NewlyDiscoveredMutations: [],
+ NewlyDiscoveredPlants: []
+ };
+}
+
+
+function FinalizeSimulationResult(
+ Result
+) {
+ Result.SuccessfulMutations = [
+ ...new Set(
+ Result.SuccessfulMutations
+ )
+ ];
+
+ Result.NewlyDiscoveredMutations = [
+ ...new Set(
+ Result.NewlyDiscoveredMutations
+ )
+ ];
+
+ Result.NewlyDiscoveredPlants = [
+ ...new Set(
+ Result.NewlyDiscoveredPlants
+ )
+ ];
+
+ Result.Dirty =
+ Result.Attempted ||
+ Result.Changed;
+
+ return Result;
+}
+
+
+function CheckAllGardenMutationsAtTime(
+ SaveData,
+ AtTime
+) {
+ const OriginalGardenIndex =
+ SaveData.ActiveGardenIndex;
+
+ const Result = {
+ Attempted: false,
+ Changed: false,
+ SuccessfulMutations: [],
+ NewlyDiscoveredMutations: [],
+ NewlyDiscoveredPlants: []
+ };
+
+ for (
+ let GardenIndex = 0;
+ GardenIndex < SaveData.Gardens.length;
+ GardenIndex++
+ ) {
+ SetActiveGardenIndex(
+ SaveData,
+ GardenIndex
+ );
+
+ const GardenResult =
+ CheckGardenMutations(
+ SaveData,
+ AtTime
+ );
+
+ MergeSimulationMutationResult(
+ Result,
+ GardenResult
+ );
+ }
+
+ SetActiveGardenIndex(
+ SaveData,
+ OriginalGardenIndex
+ );
+
+ return Result;
+}
+
+
+function GetNextPlantMaturityTime(
+ SaveData,
+ CurrentTime,
+ TargetTime
+) {
+ let NextTime = null;
+
+ for (const Garden of SaveData.Gardens) {
+ for (const Plot of Garden.Plots) {
+ if (Plot === null) {
+ continue;
+ }
+
+ const Plant =
+ Plants[Plot.Plant];
+
+ if (Plant === undefined) {
+ continue;
+ }
+
+ const GrowthTime = Math.max(
+ 0,
+ Number(
+ Plant.GrowthTime ?? 0
+ ) || 0
+ );
+
+ if (GrowthTime <= 0) {
+ continue;
+ }
+
+ const MaturityTime =
+ Number(
+ Plot.PlantedAt ?? 0
+ ) +
+ GrowthTime;
+
+ if (
+ MaturityTime <= CurrentTime ||
+ MaturityTime > TargetTime
+ ) {
+ continue;
+ }
+
+ if (
+ NextTime === null ||
+ MaturityTime < NextTime
+ ) {
+ NextTime = MaturityTime;
+ }
+ }
+ }
+
+ return NextTime;
+}
+
+
+function GetNextMutationAttemptTime(
+ SaveData,
+ CurrentTime,
+ TargetTime
+) {
+ const OriginalGardenIndex =
+ SaveData.ActiveGardenIndex;
+
+ let NextTime = null;
+
+ for (
+ let GardenIndex = 0;
+ GardenIndex < SaveData.Gardens.length;
+ GardenIndex++
+ ) {
+ SetActiveGardenIndex(
+ SaveData,
+ GardenIndex
+ );
+
+ const Candidates =
+ FindGardenMutationCandidates(
+ SaveData,
+ CurrentTime
+ );
+
+ for (const Candidate of Candidates) {
+ const CooldownUntil =
+ GetMutationCandidateCooldownUntil(
+ SaveData,
+ Candidate
+ );
+
+ const AttemptTime =
+ CooldownUntil > CurrentTime
+ ? CooldownUntil
+ : CurrentTime +
+ SimulationMinimumEventInterval;
+
+ if (AttemptTime > TargetTime) {
+ continue;
+ }
+
+ if (
+ NextTime === null ||
+ AttemptTime < NextTime
+ ) {
+ NextTime = AttemptTime;
+ }
+ }
+ }
+
+ SetActiveGardenIndex(
+ SaveData,
+ OriginalGardenIndex
+ );
+
+ return NextTime;
+}
+
+
+function GetNextSimulationEventTime(
+ SaveData,
+ CurrentTime,
+ TargetTime
+) {
+ const MaturityTime =
+ GetNextPlantMaturityTime(
+ SaveData,
+ CurrentTime,
+ TargetTime
+ );
+
+ const MutationTime =
+ GetNextMutationAttemptTime(
+ SaveData,
+ CurrentTime,
+ TargetTime
+ );
+
+ if (MaturityTime === null) {
+ return MutationTime;
+ }
+
+ if (MutationTime === null) {
+ return MaturityTime;
+ }
+
+ return Math.min(
+ MaturityTime,
+ MutationTime
+ );
+}
+
+
+function AdvanceGameSimulation(
+ SaveData,
+ TargetTime = Date.now()
+) {
+ const SafeTargetTime = Math.max(
+ 0,
+ Math.floor(
+ Number(TargetTime) ||
+ Date.now()
+ )
+ );
+
+ let CurrentTime = Math.min(
+ GetSimulationTime(
+ SaveData
+ ),
+ SafeTargetTime
+ );
+
+ const Result =
+ CreateSimulationResult(
+ CurrentTime,
+ SafeTargetTime
+ );
+
+ SetSimulationTime(
+ SaveData,
+ CurrentTime
+ );
+
+ for (
+ let Step = 0;
+ Step < MaximumSimulationSteps;
+ Step++
+ ) {
+ Result.Steps = Step + 1;
+
+ const MutationResult =
+ CheckAllGardenMutationsAtTime(
+ SaveData,
+ CurrentTime
+ );
+
+ MergeSimulationMutationResult(
+ Result,
+ MutationResult
+ );
+
+ if (CurrentTime >= SafeTargetTime) {
+ Result.CaughtUp = true;
+ break;
+ }
+
+ const NextTime =
+ GetNextSimulationEventTime(
+ SaveData,
+ CurrentTime,
+ SafeTargetTime
+ );
+
+ if (NextTime === null) {
+ CurrentTime = SafeTargetTime;
+ SetSimulationTime(
+ SaveData,
+ CurrentTime
+ );
+ Result.CaughtUp = true;
+ break;
+ }
+
+ CurrentTime = Math.min(
+ SafeTargetTime,
+ Math.max(
+ CurrentTime + 1,
+ NextTime
+ )
+ );
+
+ SetSimulationTime(
+ SaveData,
+ CurrentTime
+ );
+ }
+
+ Result.EndTime =
+ GetSimulationTime(
+ SaveData
+ );
+
+ const FinalResult =
+ FinalizeSimulationResult(
+ Result
+ );
+
+ if (
+ FinalResult.Dirty &&
+ typeof WriteLocalSave ===
+ "function"
+ ) {
+ WriteLocalSave(
+ SaveData
+ );
+ }
+
+ return FinalResult;
+}
diff --git a/Scripts/Garden/Upgrades.js b/Scripts/Garden/Upgrades.js
index 610fe8e..7762e2a 100644
--- a/Scripts/Garden/Upgrades.js
+++ b/Scripts/Garden/Upgrades.js
@@ -692,7 +692,9 @@ function TakeFertilizerUse(
function GetFutureSightCooldownRemaining(
SaveData,
- Now = Date.now()
+ Now = GetSavedSimulationTime(
+ SaveData
+ )
) {
return Math.max(
0,
@@ -705,7 +707,9 @@ function GetFutureSightCooldownRemaining(
function IsFutureSightReady(
SaveData,
- Now = Date.now()
+ Now = GetSavedSimulationTime(
+ SaveData
+ )
) {
return GetFutureSightCooldownRemaining(
SaveData,
@@ -716,7 +720,9 @@ function IsFutureSightReady(
function StartFutureSightCooldown(
SaveData,
- Now = Date.now()
+ Now = GetSavedSimulationTime(
+ SaveData
+ )
) {
SaveData.Tools ??= {};
SaveData.Tools.FutureSightCooldownUntil =