Add /lw speed command for real-time simulation acceleration
/lw speed <1-20> — run N full sim cycles (module pipeline + all
post-sim hooks) per normal tick interval.
/lw speed reset — return to real-time (1x).
The multiplier is stored on LivingWorldBootstrap and reset to 1 on
server stop. Extra cycles queue all active regions and call the full
post-sim hook chain (pollution spread, seasonal effects, climate,
warming, water runoff, dynamic cap) so ecosystem state advances
coherently rather than just running modules in isolation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import com.livingworld.core.services.ServiceRegistry;
|
||||
import com.livingworld.core.simulation.DefaultTimeService;
|
||||
import com.livingworld.core.simulation.SimulationManager;
|
||||
import com.livingworld.core.simulation.SimulationScheduler;
|
||||
import com.livingworld.core.simulation.UpdateReason;
|
||||
import com.livingworld.debug.SimulationProfiler;
|
||||
import com.livingworld.debug.DiagnosticCategory;
|
||||
import com.livingworld.debug.LivingWorldLogger;
|
||||
@@ -90,6 +91,8 @@ public final class LivingWorldBootstrap {
|
||||
private boolean serverReady;
|
||||
/** Average surface elevation (block Y) per region, sampled once by the platform layer. */
|
||||
private final Map<RegionCoordinate, Double> regionElevations = new HashMap<>();
|
||||
/** Extra sim cycles run per normal tick interval (1 = real-time, >1 = accelerated). */
|
||||
private int simSpeedMultiplier = 1;
|
||||
|
||||
/**
|
||||
* Called once during mod construction.
|
||||
@@ -212,6 +215,7 @@ public final class LivingWorldBootstrap {
|
||||
climateTracker.save(worldSaveDirectory.resolve("living_world/global_climate.dat"));
|
||||
hudEnabledPlayers.clear();
|
||||
regionElevations.clear();
|
||||
simSpeedMultiplier = 1;
|
||||
serverReady = false;
|
||||
LivingWorldLogger.info(
|
||||
DiagnosticCategory.BOOTSTRAP,
|
||||
@@ -343,16 +347,38 @@ public final class LivingWorldBootstrap {
|
||||
long previousSimulationTick = simulationManager.getSimulationTickCounter();
|
||||
simulationManager.onMinecraftServerTick();
|
||||
if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
|
||||
runPostSimHooks();
|
||||
for (int extra = 1; extra < simSpeedMultiplier; extra++) {
|
||||
for (Region r : regionManager.getActiveRegions()) {
|
||||
simulationManager.queueRegionForUpdate(
|
||||
r.getCoordinate(), 0, UpdateReason.NORMAL_ROLLING_UPDATE);
|
||||
}
|
||||
simulationManager.runSimulationCycle();
|
||||
runPostSimHooks();
|
||||
}
|
||||
regionManager.saveDirtyRegions();
|
||||
}
|
||||
}
|
||||
|
||||
private void runPostSimHooks() {
|
||||
spreadPollutionAcrossRegions();
|
||||
applySeasonalEffects();
|
||||
climateTracker.update(regionManager.getActiveRegions());
|
||||
applyClimateWarmingEffects();
|
||||
applyWaterRunoff();
|
||||
applyDynamicCapUpdate();
|
||||
regionManager.saveDirtyRegions();
|
||||
}
|
||||
|
||||
/** Sets the simulation speed multiplier (1 = real-time, max 20). */
|
||||
public int setSimSpeedMultiplier(int multiplier) {
|
||||
this.simSpeedMultiplier = Math.max(1, Math.min(20, multiplier));
|
||||
LivingWorldLogger.info(DiagnosticCategory.BOOTSTRAP,
|
||||
"Simulation speed set to " + simSpeedMultiplier + "x");
|
||||
return this.simSpeedMultiplier;
|
||||
}
|
||||
|
||||
public int getSimSpeedMultiplier() { return simSpeedMultiplier; }
|
||||
|
||||
private static final double POLLUTION_SPREAD_RATE = 0.02;
|
||||
|
||||
private void spreadPollutionAcrossRegions() {
|
||||
@@ -635,7 +661,8 @@ public final class LivingWorldBootstrap {
|
||||
() -> requireService(simulationManager, "simulationManager"),
|
||||
this::toggleHud,
|
||||
this::getAtmosphereStatusFor,
|
||||
this::getClimateStatusFor);
|
||||
this::getClimateStatusFor,
|
||||
this::setSimSpeedMultiplier);
|
||||
}
|
||||
|
||||
public Path getWorldSaveDirectory() {
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.livingworld.commands;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntUnaryOperator;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -41,7 +42,8 @@ public final class LivingWorldCommandRoot {
|
||||
() -> simulationManager,
|
||||
uuid -> false,
|
||||
css -> "Atmosphere not available.",
|
||||
css -> "Climate not available.");
|
||||
css -> "Climate not available.",
|
||||
n -> n);
|
||||
}
|
||||
|
||||
public static void registerDeferred(
|
||||
@@ -51,7 +53,8 @@ public final class LivingWorldCommandRoot {
|
||||
Supplier<SimulationManager> simulationManager,
|
||||
Function<UUID, Boolean> hudToggle,
|
||||
Function<CommandSourceStack, String> atmosphereStatus,
|
||||
Function<CommandSourceStack, String> climateStatus) {
|
||||
Function<CommandSourceStack, String> climateStatus,
|
||||
IntUnaryOperator speedSetter) {
|
||||
if (dispatcher == null) {
|
||||
throw new IllegalArgumentException("dispatcher must not be null");
|
||||
}
|
||||
@@ -122,6 +125,24 @@ public final class LivingWorldCommandRoot {
|
||||
context.getSource().sendSuccess(() -> Component.literal(info), false);
|
||||
return 1;
|
||||
}))
|
||||
.then(Commands.literal("speed")
|
||||
.then(Commands.argument("multiplier", IntegerArgumentType.integer(1, 20))
|
||||
.executes(context -> {
|
||||
int n = IntegerArgumentType.getInteger(context, "multiplier");
|
||||
int actual = speedSetter.applyAsInt(n);
|
||||
context.getSource().sendSuccess(
|
||||
() -> Component.literal("[LW] Simulation speed: " + actual + "x"),
|
||||
false);
|
||||
return actual;
|
||||
}))
|
||||
.then(Commands.literal("reset")
|
||||
.executes(context -> {
|
||||
speedSetter.applyAsInt(1);
|
||||
context.getSource().sendSuccess(
|
||||
() -> Component.literal("[LW] Simulation speed reset to 1x"),
|
||||
false);
|
||||
return 1;
|
||||
})))
|
||||
.then(RegionSetCommand.build(regionManager)));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user