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.DefaultTimeService;
|
||||||
import com.livingworld.core.simulation.SimulationManager;
|
import com.livingworld.core.simulation.SimulationManager;
|
||||||
import com.livingworld.core.simulation.SimulationScheduler;
|
import com.livingworld.core.simulation.SimulationScheduler;
|
||||||
|
import com.livingworld.core.simulation.UpdateReason;
|
||||||
import com.livingworld.debug.SimulationProfiler;
|
import com.livingworld.debug.SimulationProfiler;
|
||||||
import com.livingworld.debug.DiagnosticCategory;
|
import com.livingworld.debug.DiagnosticCategory;
|
||||||
import com.livingworld.debug.LivingWorldLogger;
|
import com.livingworld.debug.LivingWorldLogger;
|
||||||
@@ -90,6 +91,8 @@ public final class LivingWorldBootstrap {
|
|||||||
private boolean serverReady;
|
private boolean serverReady;
|
||||||
/** Average surface elevation (block Y) per region, sampled once by the platform layer. */
|
/** Average surface elevation (block Y) per region, sampled once by the platform layer. */
|
||||||
private final Map<RegionCoordinate, Double> regionElevations = new HashMap<>();
|
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.
|
* Called once during mod construction.
|
||||||
@@ -212,6 +215,7 @@ public final class LivingWorldBootstrap {
|
|||||||
climateTracker.save(worldSaveDirectory.resolve("living_world/global_climate.dat"));
|
climateTracker.save(worldSaveDirectory.resolve("living_world/global_climate.dat"));
|
||||||
hudEnabledPlayers.clear();
|
hudEnabledPlayers.clear();
|
||||||
regionElevations.clear();
|
regionElevations.clear();
|
||||||
|
simSpeedMultiplier = 1;
|
||||||
serverReady = false;
|
serverReady = false;
|
||||||
LivingWorldLogger.info(
|
LivingWorldLogger.info(
|
||||||
DiagnosticCategory.BOOTSTRAP,
|
DiagnosticCategory.BOOTSTRAP,
|
||||||
@@ -343,16 +347,38 @@ public final class LivingWorldBootstrap {
|
|||||||
long previousSimulationTick = simulationManager.getSimulationTickCounter();
|
long previousSimulationTick = simulationManager.getSimulationTickCounter();
|
||||||
simulationManager.onMinecraftServerTick();
|
simulationManager.onMinecraftServerTick();
|
||||||
if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
|
if (simulationManager.getSimulationTickCounter() != previousSimulationTick) {
|
||||||
spreadPollutionAcrossRegions();
|
runPostSimHooks();
|
||||||
applySeasonalEffects();
|
for (int extra = 1; extra < simSpeedMultiplier; extra++) {
|
||||||
climateTracker.update(regionManager.getActiveRegions());
|
for (Region r : regionManager.getActiveRegions()) {
|
||||||
applyClimateWarmingEffects();
|
simulationManager.queueRegionForUpdate(
|
||||||
applyWaterRunoff();
|
r.getCoordinate(), 0, UpdateReason.NORMAL_ROLLING_UPDATE);
|
||||||
applyDynamicCapUpdate();
|
}
|
||||||
|
simulationManager.runSimulationCycle();
|
||||||
|
runPostSimHooks();
|
||||||
|
}
|
||||||
regionManager.saveDirtyRegions();
|
regionManager.saveDirtyRegions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runPostSimHooks() {
|
||||||
|
spreadPollutionAcrossRegions();
|
||||||
|
applySeasonalEffects();
|
||||||
|
climateTracker.update(regionManager.getActiveRegions());
|
||||||
|
applyClimateWarmingEffects();
|
||||||
|
applyWaterRunoff();
|
||||||
|
applyDynamicCapUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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 static final double POLLUTION_SPREAD_RATE = 0.02;
|
||||||
|
|
||||||
private void spreadPollutionAcrossRegions() {
|
private void spreadPollutionAcrossRegions() {
|
||||||
@@ -635,7 +661,8 @@ public final class LivingWorldBootstrap {
|
|||||||
() -> requireService(simulationManager, "simulationManager"),
|
() -> requireService(simulationManager, "simulationManager"),
|
||||||
this::toggleHud,
|
this::toggleHud,
|
||||||
this::getAtmosphereStatusFor,
|
this::getAtmosphereStatusFor,
|
||||||
this::getClimateStatusFor);
|
this::getClimateStatusFor,
|
||||||
|
this::setSimSpeedMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Path getWorldSaveDirectory() {
|
public Path getWorldSaveDirectory() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.livingworld.commands;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
import java.util.function.IntUnaryOperator;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -41,7 +42,8 @@ public final class LivingWorldCommandRoot {
|
|||||||
() -> simulationManager,
|
() -> simulationManager,
|
||||||
uuid -> false,
|
uuid -> false,
|
||||||
css -> "Atmosphere not available.",
|
css -> "Atmosphere not available.",
|
||||||
css -> "Climate not available.");
|
css -> "Climate not available.",
|
||||||
|
n -> n);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerDeferred(
|
public static void registerDeferred(
|
||||||
@@ -51,7 +53,8 @@ public final class LivingWorldCommandRoot {
|
|||||||
Supplier<SimulationManager> simulationManager,
|
Supplier<SimulationManager> simulationManager,
|
||||||
Function<UUID, Boolean> hudToggle,
|
Function<UUID, Boolean> hudToggle,
|
||||||
Function<CommandSourceStack, String> atmosphereStatus,
|
Function<CommandSourceStack, String> atmosphereStatus,
|
||||||
Function<CommandSourceStack, String> climateStatus) {
|
Function<CommandSourceStack, String> climateStatus,
|
||||||
|
IntUnaryOperator speedSetter) {
|
||||||
if (dispatcher == null) {
|
if (dispatcher == null) {
|
||||||
throw new IllegalArgumentException("dispatcher must not be null");
|
throw new IllegalArgumentException("dispatcher must not be null");
|
||||||
}
|
}
|
||||||
@@ -122,6 +125,24 @@ public final class LivingWorldCommandRoot {
|
|||||||
context.getSource().sendSuccess(() -> Component.literal(info), false);
|
context.getSource().sendSuccess(() -> Component.literal(info), false);
|
||||||
return 1;
|
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)));
|
.then(RegionSetCommand.build(regionManager)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user