diff --git a/src/main/java/com/livingworld/commands/EcoDemoCommand.java b/src/main/java/com/livingworld/commands/EcoDemoCommand.java
new file mode 100644
index 0000000..7cb948b
--- /dev/null
+++ b/src/main/java/com/livingworld/commands/EcoDemoCommand.java
@@ -0,0 +1,167 @@
+package com.livingworld.commands;
+
+import java.util.function.Supplier;
+
+import com.livingworld.core.LivingWorldConstants;
+import com.livingworld.modules.atmosphere.AtmosphereModule;
+import com.livingworld.modules.atmosphere.AtmosphereRegionData;
+import com.livingworld.modules.pollution.PollutionModule;
+import com.livingworld.modules.pollution.PollutionRegionData;
+import com.livingworld.modules.recovery.RecoveryModule;
+import com.livingworld.modules.recovery.RecoveryRegionData;
+import com.livingworld.modules.recovery.SuccessionStage;
+import com.livingworld.modules.soil.SoilModule;
+import com.livingworld.modules.soil.SoilRegionData;
+import com.livingworld.modules.vegetation.VegetationModule;
+import com.livingworld.modules.vegetation.VegetationRegionData;
+import com.livingworld.modules.water.WaterModule;
+import com.livingworld.modules.water.WaterRegionData;
+import com.livingworld.regions.Region;
+import com.livingworld.regions.RegionCoordinate;
+import com.livingworld.regions.RegionManager;
+import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.brigadier.exceptions.CommandSyntaxException;
+
+import net.minecraft.commands.CommandSourceStack;
+import net.minecraft.commands.Commands;
+import net.minecraft.network.chat.Component;
+import net.minecraft.server.level.ServerPlayer;
+
+/**
+ * One-command demo presets for watching ecosystem transformation.
+ *
+ *
+ * /lw demo degrade — forces MATURE_FOREST then applies severe drought + dead soil
+ * so the region visibly regresses toward BARREN.
+ * Recommended speed: /lw speed 20
+ *
+ * /lw demo recover — resets soil and water to lush conditions so the region
+ * advances from wherever it is back toward MATURE_FOREST.
+ * Recommended speed: /lw speed 50
+ *
+ */
+public final class EcoDemoCommand {
+
+ private EcoDemoCommand() {}
+
+ public static LiteralArgumentBuilder build(Supplier rm) {
+ return Commands.literal("demo")
+ .then(Commands.literal("degrade")
+ .executes(ctx -> runDegrade(ctx.getSource(), rm.get())))
+ .then(Commands.literal("recover")
+ .executes(ctx -> runRecover(ctx.getSource(), rm.get())));
+ }
+
+ // ------------------------------------------------------------------
+ // Presets
+ // ------------------------------------------------------------------
+
+ private static int runDegrade(CommandSourceStack src, RegionManager rm) {
+ return applyPreset(src, rm, "DEGRADE", region -> {
+ // Force starting point: peak forest.
+ region.getModuleData().put(RecoveryModule.MODULE_ID,
+ new RecoveryRegionData(SuccessionStage.MATURE_FOREST, 0.0, 0.0,
+ SuccessionStage.MATURE_FOREST));
+
+ // Lush vegetation so it visibly dies rather than starting at zero.
+ region.getModuleData().put(VegetationModule.MODULE_ID,
+ new VegetationRegionData(100.0, 80.0, 80.0, 70.0, 0.0));
+
+ // Dead soil: near-zero fertility, high erosion and contamination.
+ // soilQuality ≈ 5 − 8×0.4 − 8×0.3 = 5 − 3.2 − 2.4 = −0.6 → 0
+ // That floor triggers badConditions in VegetationModule and
+ // conditionsMissedForRegression in RecoveryModule every cycle.
+ SoilRegionData soil = SoilRegionData.defaults();
+ soil.setFertility(5.0);
+ soil.setMoisture(5.0);
+ soil.setContamination(8.0);
+ soil.setErosion(8.0);
+ region.getModuleData().put(SoilModule.MODULE_ID, soil);
+
+ // Severe drought — water availability near zero.
+ WaterRegionData water = WaterRegionData.defaults();
+ water.setWaterAvailability(5.0);
+ water.setDroughtRisk(90.0);
+ water.setPurificationCapacity(5.0);
+ region.getModuleData().put(WaterModule.MODULE_ID, water);
+
+ // Clear pollution so it doesn't confuse the read-out (soil drives the decay here).
+ region.getModuleData().put(PollutionModule.MODULE_ID, PollutionRegionData.defaults());
+
+ // Dry sky: low rain so per-player weather reflects the drought.
+ AtmosphereRegionData atm = new AtmosphereRegionData(0.05, 0.0);
+ region.getModuleData().put(AtmosphereModule.MODULE_ID, atm);
+ }, "Stage forced to MATURE_FOREST | Soil destroyed | Drought=90 | Use /lw speed 20 to watch");
+ }
+
+ private static int runRecover(CommandSourceStack src, RegionManager rm) {
+ return applyPreset(src, rm, "RECOVER", region -> {
+ // Raise cap — region must be allowed to reach MATURE_FOREST again.
+ RecoveryRegionData rec = region.getModuleData()
+ .get(RecoveryModule.MODULE_ID, RecoveryRegionData.class)
+ .orElseGet(RecoveryRegionData::defaults);
+ rec.setMaxSuccessionStage(SuccessionStage.MATURE_FOREST);
+ region.getModuleData().put(RecoveryModule.MODULE_ID, rec);
+
+ // Rich, clean soil — contamination and erosion both zeroed.
+ SoilRegionData soil = SoilRegionData.defaults();
+ soil.setFertility(85.0);
+ soil.setMoisture(75.0);
+ soil.setContamination(0.0);
+ soil.setErosion(0.0);
+ region.getModuleData().put(SoilModule.MODULE_ID, soil);
+
+ // Plentiful water, no drought.
+ WaterRegionData water = WaterRegionData.defaults();
+ water.setWaterAvailability(85.0);
+ water.setDroughtRisk(5.0);
+ water.setPurificationCapacity(80.0);
+ region.getModuleData().put(WaterModule.MODULE_ID, water);
+
+ // Clean air.
+ region.getModuleData().put(PollutionModule.MODULE_ID, PollutionRegionData.defaults());
+
+ // Good rain signal in the atmosphere.
+ AtmosphereRegionData atm = new AtmosphereRegionData(0.75, 0.0);
+ region.getModuleData().put(AtmosphereModule.MODULE_ID, atm);
+
+ // Seed minimal grass so vegetation succession has something to start from.
+ // (Shrubs and trees grow naturally from here.)
+ VegetationRegionData veg = region.getModuleData()
+ .get(VegetationModule.MODULE_ID, VegetationRegionData.class)
+ .orElseGet(VegetationRegionData::defaults);
+ veg.setGrassPressure(Math.max(veg.getGrassPressure(), 5.0));
+ veg.setDeadVegetation(0.0);
+ region.getModuleData().put(VegetationModule.MODULE_ID, veg);
+ }, "Cap raised to MATURE_FOREST | Soil+water restored | Use /lw speed 50 to watch");
+ }
+
+ // ------------------------------------------------------------------
+
+ private static int applyPreset(CommandSourceStack src, RegionManager rm,
+ String label, RegionMutation mutation, String hint) {
+ if (rm == null) { src.sendFailure(Component.literal("Region manager not ready.")); return 0; }
+ ServerPlayer player;
+ try { player = src.getPlayerOrException(); }
+ catch (CommandSyntaxException e) {
+ src.sendFailure(Component.literal("/lw demo must be run by a player."));
+ return 0;
+ }
+
+ String dimId = player.level().dimension().location().toString();
+ int rx = (int) Math.floor(player.getX() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
+ int rz = (int) Math.floor(player.getZ() / (LivingWorldConstants.DEFAULT_REGION_SIZE_CHUNKS * 16.0));
+ RegionCoordinate coord = new RegionCoordinate(dimId, rx, rz);
+
+ Region region = rm.getOrCreateRegion(coord);
+ mutation.apply(region);
+ rm.markDirty(region);
+
+ src.sendSuccess(() -> Component.literal(
+ "[LW] Demo:" + label + " applied to (" + rx + "," + rz + ") — " + hint), false);
+ return 1;
+ }
+
+ @FunctionalInterface
+ private interface RegionMutation { void apply(Region region); }
+}
diff --git a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java
index d039888..7467854 100644
--- a/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java
+++ b/src/main/java/com/livingworld/commands/LivingWorldCommandRoot.java
@@ -143,7 +143,8 @@ public final class LivingWorldCommandRoot {
false);
return 1;
})))
- .then(RegionSetCommand.build(regionManager)));
+ .then(RegionSetCommand.build(regionManager))
+ .then(EcoDemoCommand.build(regionManager)));
}
private static int toggleHud(CommandSourceStack source, Function hudToggle) {