diff --git a/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java b/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java
index 7dbebee..7ceb058 100644
--- a/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java
+++ b/src/main/java/com/mythlane/gravityflip/GravityFlipPlugin.java
@@ -5,9 +5,7 @@ import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.util.Config;
-import com.mythlane.gravityflip.command.DumpParticlesCommand;
import com.mythlane.gravityflip.config.GravityFlipConfig;
-import com.mythlane.gravityflip.debug.RegionEnterNotifier;
import com.mythlane.gravityflip.physics.FallDamageGuard;
import com.mythlane.gravityflip.physics.FallDamageSuppressorSystem;
import com.mythlane.gravityflip.physics.GravityApplier;
@@ -83,15 +81,6 @@ public class GravityFlipPlugin extends JavaPlugin {
this.tickLoop = new RegionTickLoop(registry, gravityApplier, regionVisualizer, th ->
getLogger().at(Level.WARNING).withCause(th).log("detectTick failed"));
- // Debug enter/exit notifier — opt-in via env var GRAVITYFLIP_DEBUG_NOTIFY (or sysprop
- // gravityflip.debugNotify). Emits chat + INFO log on every region transition. Off by
- // default to keep prod chat/logs clean.
- if (isDebugNotifyEnabled()) {
- this.tickLoop.setEnterNotifier(new RegionEnterNotifier(registry,
- th -> getLogger().at(Level.WARNING).withCause(th).log("regionEnterNotify failed"),
- msg -> getLogger().at(Level.INFO).log("%s", msg)));
- }
-
// Lazy world resolution — see setup() comment.
this.tickLoop.startWithDelay(2_000L, () -> {
Universe u = Universe.get();
@@ -111,37 +100,6 @@ public class GravityFlipPlugin extends JavaPlugin {
getLogger().at(Level.INFO).log(
"Gravity Flip enabled — %d region(s) loaded, detector @100ms, gravity inversion active",
cfg.getRegions().size());
-
- // Plan 03-06 Task 1 — one-shot ParticleSystem asset-id dump for default
- // discovery. Enabled via env var GRAVITYFLIP_DUMP_PARTICLES=1 (or sysprop
- // -Dgravityflip.dumpParticles=true). No-op otherwise. See
- // DumpParticlesCommand javadoc for the chosen API rationale.
- if (DumpParticlesCommand.isEnabled()) {
- try {
- int n = DumpParticlesCommand.dump(line ->
- getLogger().at(Level.INFO).log("%s", line));
- getLogger().at(Level.INFO).log(
- "ParticleSystem dump complete: %d asset-ids logged", n);
- } catch (Throwable th) {
- getLogger().at(Level.WARNING).withCause(th)
- .log("ParticleSystem dump failed");
- }
- }
- }
-
- /**
- * Env var {@code GRAVITYFLIP_DEBUG_NOTIFY=1} or sysprop {@code -Dgravityflip.debugNotify=true}
- * enables the chat + log enter/exit notifier. Off by default — debug tool only.
- */
- private static boolean isDebugNotifyEnabled() {
- return truthy(System.getenv("GRAVITYFLIP_DEBUG_NOTIFY"))
- || truthy(System.getProperty("gravityflip.debugNotify"));
- }
-
- private static boolean truthy(String v) {
- if (v == null) return false;
- String s = v.trim().toLowerCase();
- return s.equals("1") || s.equals("true") || s.equals("yes") || s.equals("on");
}
@Override
diff --git a/src/main/java/com/mythlane/gravityflip/command/DumpParticlesCommand.java b/src/main/java/com/mythlane/gravityflip/command/DumpParticlesCommand.java
deleted file mode 100644
index 6d479ea..0000000
--- a/src/main/java/com/mythlane/gravityflip/command/DumpParticlesCommand.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.mythlane.gravityflip.command;
-
-import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
-import com.hypixel.hytale.server.core.asset.type.particle.config.ParticleSystem;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-import java.util.logging.Logger;
-
-/**
- * Discovery utility — dumps all loaded {@link ParticleSystem} asset-ids to the
- * plugin logger. Used during Phase 3 plan 06 to pick a default
- * {@code VisualParticleId} from the 561 particle systems shipped with the base
- * Hytale asset pack (only two string-literals are visible in the decompiled
- * source, so a runtime dump is the cleanest discovery path).
- *
- *
API used: {@code ParticleSystem.getAssetMap().getAssetMap().keySet()}
- * — static accessor resolved via {@link com.hypixel.hytale.assetstore.AssetRegistry}.
- * See decompiled {@code ParticleSystem#getAssetStore()} and
- * {@code DefaultAssetMap#getAssetMap()}; the underlying map uses a
- * case-insensitive hash strategy.
- *
- *
Trigger: set the environment variable
- * {@code GRAVITYFLIP_DUMP_PARTICLES=1} (or {@code =true}) before launching the
- * server. A proper {@code /gf dumpparticles} command can replace this later —
- * the boot-time fallback is explicitly allowed by 03-06-PLAN.md Task 1.
- *
- *
This class intentionally does not extend
- * {@code com.hypixel.hytale.server.core.command.system.basecommands.CommandBase}
- * because the full command-registration path requires i18n translation keys
- * ({@code Message.translation(...)}) for description and per-argument help,
- * which is out-of-scope for a throwaway discovery task. Name "Command" is kept
- * to satisfy the plan artifact path.
- */
-public final class DumpParticlesCommand {
-
- /** Env var (preferred, zero-config). */
- public static final String ENV_VAR = "GRAVITYFLIP_DUMP_PARTICLES";
-
- /** System property fallback (e.g. {@code -Dgravityflip.dumpParticles=true}). */
- public static final String SYS_PROP = "gravityflip.dumpParticles";
-
- private static final String LOG_PREFIX = "[dumpparticles]";
-
- private DumpParticlesCommand() {}
-
- /**
- * @return true if either {@link #ENV_VAR} or {@link #SYS_PROP} is set to a
- * truthy value ({@code "1"}, {@code "true"}, {@code "yes"}, case-insensitive).
- */
- public static boolean isEnabled() {
- return isTruthy(System.getenv(ENV_VAR)) || isTruthy(System.getProperty(SYS_PROP));
- }
-
- private static boolean isTruthy(String v) {
- if (v == null) return false;
- String s = v.trim().toLowerCase();
- return s.equals("1") || s.equals("true") || s.equals("yes") || s.equals("on");
- }
-
- /**
- * Dump all loaded ParticleSystem asset-ids via the supplied logger, one id
- * per line prefixed with {@value #LOG_PREFIX}. Safe to call from
- * {@code JavaPlugin.start()} (post-LoadAssetEvent). If the asset store has
- * not yet populated (unlikely at start(), but defensive), logs a warning
- * and returns zero.
- *
- * @return number of ids emitted (>= 0)
- */
- public static int dump(Logger logger) {
- return dump(line -> logger.info(line));
- }
-
- /**
- * Testable variant — accepts any line consumer (logger.info, println, list::add).
- */
- public static int dump(Consumer lineSink) {
- DefaultAssetMap map;
- try {
- map = ParticleSystem.getAssetMap();
- } catch (Throwable th) {
- lineSink.accept(LOG_PREFIX + " ERROR: ParticleSystem asset store not available: " + th);
- return 0;
- }
- if (map == null) {
- lineSink.accept(LOG_PREFIX + " ERROR: ParticleSystem.getAssetMap() returned null");
- return 0;
- }
-
- // DefaultAssetMap#getAssetMap() returns an unmodifiable view of the
- // underlying Map. Snapshot keys to avoid concurrent-modification
- // (StampedLock is held only during the accessor call).
- List ids = new ArrayList<>(map.getAssetMap().keySet());
- Collections.sort(ids);
-
- lineSink.accept(LOG_PREFIX + " BEGIN — " + ids.size() + " ParticleSystem asset-ids loaded:");
- for (String id : ids) {
- lineSink.accept(LOG_PREFIX + " " + id);
- }
- lineSink.accept(LOG_PREFIX + " END");
- return ids.size();
- }
-}
diff --git a/src/main/java/com/mythlane/gravityflip/debug/RegionEnterNotifier.java b/src/main/java/com/mythlane/gravityflip/debug/RegionEnterNotifier.java
deleted file mode 100644
index 92c788a..0000000
--- a/src/main/java/com/mythlane/gravityflip/debug/RegionEnterNotifier.java
+++ /dev/null
@@ -1,150 +0,0 @@
-package com.mythlane.gravityflip.debug;
-
-import com.hypixel.hytale.component.ComponentType;
-import com.hypixel.hytale.component.Store;
-import com.hypixel.hytale.math.shape.Box;
-import com.hypixel.hytale.protocol.FormattedMessage;
-import com.hypixel.hytale.protocol.packets.interface_.ChatType;
-import com.hypixel.hytale.protocol.packets.interface_.ServerMessage;
-import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
-import com.hypixel.hytale.server.core.universe.PlayerRef;
-import com.hypixel.hytale.server.core.universe.world.World;
-import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
-import com.mythlane.gravityflip.region.GravityFlipRegion;
-import com.mythlane.gravityflip.region.RegionRegistry;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
-
-/**
- * Debug: envoie un message chat au joueur à l'entrée/sortie d'une région gravity-flip.
- * Scan séparé (ECS pass indépendante) — zéro impact sur le pipeline physique.
- */
-public final class RegionEnterNotifier {
-
- private final RegionRegistry registry;
- private final Consumer errorHandler;
- private final Consumer logHandler;
- private final Map> lastRegions = new ConcurrentHashMap<>();
-
- public RegionEnterNotifier(RegionRegistry registry, Consumer errorHandler) {
- this(registry, errorHandler, null);
- }
-
- public RegionEnterNotifier(RegionRegistry registry, Consumer errorHandler,
- Consumer logHandler) {
- this.registry = registry;
- this.errorHandler = errorHandler == null ? t -> {} : errorHandler;
- this.logHandler = logHandler == null ? s -> {} : logHandler;
- }
-
- public void tick(World world) {
- if (world == null) return;
- try {
- world.execute(() -> {
- try { doTick(world); } catch (Throwable th) { errorHandler.accept(th); }
- });
- } catch (Throwable th) {
- errorHandler.accept(th);
- }
- }
-
- private void doTick(World world) {
- List enabled = new ArrayList<>();
- for (GravityFlipRegion r : registry.all()) if (r.isEnabled()) enabled.add(r);
- if (enabled.isEmpty()) { lastRegions.clear(); return; }
-
- Store store = world.getEntityStore().getStore();
- ComponentType TRANSFORM = TransformComponent.getComponentType();
- ComponentType PLAYER = PlayerRef.getComponentType();
-
- Map> current = new ConcurrentHashMap<>();
- Map refs = new ConcurrentHashMap<>();
-
- store.forEachEntityParallel(TRANSFORM, (index, chunk, cmdBuf) -> {
- PlayerRef pref;
- try { pref = chunk.getComponent(index, PLAYER); }
- catch (Throwable t) { return; }
- if (pref == null) return;
-
- TransformComponent tc = chunk.getComponent(index, TRANSFORM);
- com.hypixel.hytale.math.vector.Vector3d pos = tc.getPosition();
- double x = pos.x, y = pos.y, z = pos.z;
-
- UUID uuid = pref.getUuid();
- refs.putIfAbsent(uuid, pref);
- Set hit = current.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet());
- for (GravityFlipRegion r : enabled) {
- Box box = r.asBox();
- if (box.containsPosition(x, y, z)) hit.add(r.getName());
- }
- });
-
- // Diff & chat
- for (Map.Entry> e : current.entrySet()) {
- UUID uuid = e.getKey();
- Set now = e.getValue();
- Set prev = lastRegions.getOrDefault(uuid, Set.of());
- PlayerRef pref = refs.get(uuid);
- if (pref == null) continue;
-
- Set entered = new HashSet<>(now);
- entered.removeAll(prev);
- for (String rn : entered) {
- GravityFlipRegion region = findByName(enabled, rn);
- if (region != null) sendEnter(pref, region);
- }
-
- Set exited = new HashSet<>(prev);
- exited.removeAll(now);
- for (String rn : exited) sendExit(pref, rn);
- }
-
- // Update last state: replace with current, drop entries for players now in no region
- lastRegions.clear();
- for (Map.Entry> e : current.entrySet()) {
- if (!e.getValue().isEmpty()) lastRegions.put(e.getKey(), new HashSet<>(e.getValue()));
- }
- }
-
- private static GravityFlipRegion findByName(List list, String name) {
- for (GravityFlipRegion r : list) if (r.getName().equals(name)) return r;
- return null;
- }
-
- private void sendEnter(PlayerRef pref, GravityFlipRegion r) {
- String msg = String.format(
- "[GF] ENTER %s | Mode=%s | Particle=%s | Density=%.2f | Force=%.2f | FallDmg=%s | Grace=%dms",
- r.getName(),
- r.getVisualMode(),
- r.getVisualParticleId(),
- r.getVisualParticleDensity(),
- r.getVerticalForce(),
- r.isFallDamage(),
- r.getGracePeriodMs());
- sendChat(pref, msg);
- try { logHandler.accept(pref.getUsername() + " " + msg); } catch (Throwable t) { errorHandler.accept(t); }
- }
-
- private void sendExit(PlayerRef pref, String regionName) {
- String msg = "[GF] EXIT " + regionName;
- sendChat(pref, msg);
- try { logHandler.accept(pref.getUsername() + " " + msg); } catch (Throwable t) { errorHandler.accept(t); }
- }
-
- private void sendChat(PlayerRef pref, String text) {
- try {
- FormattedMessage fm = new FormattedMessage();
- fm.rawText = text;
- pref.getPacketHandler().writeNoCache(new ServerMessage(ChatType.Chat, fm));
- } catch (Throwable th) {
- errorHandler.accept(th);
- }
- }
-}
diff --git a/src/main/java/com/mythlane/gravityflip/tick/RegionTickLoop.java b/src/main/java/com/mythlane/gravityflip/tick/RegionTickLoop.java
index e53e6f3..a584607 100644
--- a/src/main/java/com/mythlane/gravityflip/tick/RegionTickLoop.java
+++ b/src/main/java/com/mythlane/gravityflip/tick/RegionTickLoop.java
@@ -1,7 +1,6 @@
package com.mythlane.gravityflip.tick;
import com.hypixel.hytale.server.core.universe.world.World;
-import com.mythlane.gravityflip.debug.RegionEnterNotifier;
import com.mythlane.gravityflip.physics.GravityApplier;
import com.mythlane.gravityflip.region.RegionRegistry;
import com.mythlane.gravityflip.viz.RegionVisualizer;
@@ -37,7 +36,6 @@ public final class RegionTickLoop {
private final RegionRegistry registry;
private final GravityApplier gravityApplier;
private final RegionVisualizer regionVisualizer;
- private volatile RegionEnterNotifier enterNotifier;
public RegionTickLoop(RegionRegistry registry, Consumer errorHandler) {
this(registry, null, null, errorHandler);
@@ -88,9 +86,6 @@ public final class RegionTickLoop {
if (regionVisualizer != null) {
regionVisualizer.visualize(w, registry.currentSnapshot(w));
}
- if (enterNotifier != null) {
- enterNotifier.tick(w);
- }
};
scheduleGuarded(initialDelayMs, tick);
}
@@ -120,7 +115,4 @@ public final class RegionTickLoop {
}
public ScheduledFuture> future() { return future; }
-
- /** Debug-only: branche un {@link RegionEnterNotifier} appelé après visualize() chaque tick. */
- public void setEnterNotifier(RegionEnterNotifier notifier) { this.enterNotifier = notifier; }
}
diff --git a/src/main/java/com/mythlane/gravityflip/viz/RegionVisualizer.java b/src/main/java/com/mythlane/gravityflip/viz/RegionVisualizer.java
index 11cb1c3..19e352f 100644
--- a/src/main/java/com/mythlane/gravityflip/viz/RegionVisualizer.java
+++ b/src/main/java/com/mythlane/gravityflip/viz/RegionVisualizer.java
@@ -126,8 +126,6 @@ public final class RegionVisualizer {
private final Map lastEmitMs = new ConcurrentHashMap<>();
/** Ids déjà warned comme invalides — évite le log-spam par tick. */
private final KeySetView warnedInvalidIds = ConcurrentHashMap.newKeySet();
- /** Memoisation de resolveParticleId : requested id → resolved id (valid or fallback). */
- private final Map resolvedIdCache = new ConcurrentHashMap<>();
public RegionVisualizer(Consumer errorHandler) {
this(errorHandler, DEFAULT_DEBUG_EMITTER, DEFAULT_PARTICLE_EMITTER,
@@ -242,27 +240,20 @@ public final class RegionVisualizer {
if (requested == null || requested.isEmpty()) {
return DEFAULT_PARTICLE_ID;
}
- String cached = resolvedIdCache.get(requested);
- if (cached != null) return cached;
- String resolved;
try {
if (ParticleSystem.getAssetMap().getAsset(requested) != null) {
- resolved = requested;
- } else {
- if (warnedInvalidIds.add(requested)) {
- java.util.logging.Logger.getLogger(RegionVisualizer.class.getName())
- .warning("[RegionVisualizer] Unknown VisualParticleId '" + requested
- + "' — falling back to '" + DEFAULT_PARTICLE_ID + "'");
- }
- resolved = DEFAULT_PARTICLE_ID;
+ return requested;
}
+ if (warnedInvalidIds.add(requested)) {
+ java.util.logging.Logger.getLogger(RegionVisualizer.class.getName())
+ .warning("[RegionVisualizer] Unknown VisualParticleId '" + requested
+ + "' — falling back to '" + DEFAULT_PARTICLE_ID + "'");
+ }
+ return DEFAULT_PARTICLE_ID;
} catch (Throwable th) {
- // AssetMap indisponible (tests hors serveur) — fail-open sans caching
- // (le premier appel prod bootera le cache avec la valeur définitive).
+ // AssetMap indisponible (tests hors serveur) — fail-open.
return requested;
}
- resolvedIdCache.put(requested, resolved);
- return resolved;
}
/** Clear immédiat de toutes les shapes debug côté clients (appelé au shutdown). */