refactor: clean GSD comments and translate remaining Java sources to English

- Reduce javadocs to one-liners across config/region/physics/tick/viz/plugin root

- Translate residual French comments; no behavioural change

- Tests adjusted where assertions referenced French strings
This commit is contained in:
2026-04-24 17:25:38 +02:00
parent 6b28dc2d2a
commit 6a830ed285
19 changed files with 163 additions and 607 deletions
@@ -15,15 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Round-trip tests for {@link GravityFlipConfig#CODEC}. Critical guarantees:
* <ul>
* <li>Regions list is always non-null (empty by default).</li>
* <li>List elements survive encode -> decode in order.</li>
* <li>Decoded list is MUTABLE — Phase 4 command handlers depend on this.
* Guard against an accidental {@code List.of(arr)} regression.</li>
* </ul>
*/
/** Round-trip tests for {@link GravityFlipConfig#CODEC} — non-null regions list, order preserved, list remains mutable. */
class GravityFlipConfigCodecTest {
@Test
@@ -49,7 +41,6 @@ class GravityFlipConfigCodecTest {
@Test
void roundTripOfEmptyListYieldsNonNullEmptyList() {
GravityFlipConfig src = new GravityFlipConfig();
// src.regions is the default empty ArrayList.
GravityFlipConfig decoded = roundTrip(src);
assertNotNull(decoded.getRegions(), "decoded regions list must never be null");
@@ -63,8 +54,7 @@ class GravityFlipConfigCodecTest {
GravityFlipConfig decoded = roundTrip(src);
// CRITICAL: must not throw UnsupportedOperationException.
// Phase 4 commands (define / delete / toggle) all mutate this list.
// Must not throw UnsupportedOperationException — command handlers depend on a mutable list.
assertDoesNotThrow(() -> decoded.getRegions().add(region("added", 2, 2, 2, 3, 3, 3)));
assertDoesNotThrow(() -> decoded.getRegions().remove(0));
assertTrue(decoded.getRegions() instanceof ArrayList,
@@ -86,7 +76,6 @@ class GravityFlipConfigCodecTest {
return GravityFlipConfig.CODEC.decode(encoded, info);
}
// Suppress unused-import warning if List is not directly referenced in any final assertion.
@SuppressWarnings("unused")
private static List<GravityFlipRegion> typeAnchor() { return null; }
}
@@ -10,10 +10,7 @@ import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Pure-data tests for {@link FallDamageGuard} — no Hytale runtime dependency.
* Covers entry / in-region / exit / grace-window / re-entry / FallDamage=true override.
*/
/** Pure-data tests for {@link FallDamageGuard} — entry, in-region, exit, grace-window, re-entry. */
class FallDamageGuardTest {
@Test
@@ -69,8 +66,7 @@ class FallDamageGuardTest {
GravityFlipRegion region = region(false, 2500);
guard.markInRegion(uuid, region);
guard.markExit(uuid, region, 1000L);
guard.markInRegion(uuid, region); // re-enter
// In-region again with FallDamage=false → immediate suppression, grace reset.
guard.markInRegion(uuid, region);
assertTrue(guard.shouldSuppressFallDamage(uuid, 1500L));
}
@@ -82,7 +78,6 @@ class FallDamageGuardTest {
GravityFlipRegion allowed = region(true, 2500);
guard.markInRegion(uuid, suppressed);
guard.markExit(uuid, suppressed, 1000L);
// New region has FallDamage=true → override immediately.
guard.markInRegion(uuid, allowed);
assertFalse(guard.shouldSuppressFallDamage(uuid, 1500L));
}
@@ -8,14 +8,7 @@ import java.util.UUID;
import static org.junit.jupiter.api.Assertions.*;
/**
* Pure-diff + tracker-semantics tests for {@link GravityApplier}.
*
* <p>Pas de mocks (pattern Phase 02-02 deviation #4). Pas de runtime Hytale requis —
* les tests 5 et 6 utilisent le hook package-private {@code __updateTrackerForTest} et
* la vue {@code previouslyInvertedView()} pour valider la sémantique du tracker sans
* toucher à {@code World} / {@code Store}.
*/
/** Pure-diff + tracker-semantics tests for {@link GravityApplier}. */
class GravityApplierDiffTest {
@Test
@@ -73,16 +66,12 @@ class GravityApplierDiffTest {
applier.__updateTrackerForTest(new HashSet<>(Set.of(c)));
assertEquals(Set.of(c), applier.previouslyInvertedView());
// View is immutable.
Set<UUID> view = applier.previouslyInvertedView();
assertThrows(UnsupportedOperationException.class, () -> view.add(UUID.randomUUID()));
}
// NOTE (Rule 3 deviation — Plan 03-02) : les tests suivants ciblent la seam pure
// `buildFlaggedDecision(double, double, boolean)` au lieu de `buildPhysicsValuesWithFlag`
// parce que le static init de `PhysicsValues` déclenche un `ExceptionInInitializerError`
// hors runtime Hytale (dépendance ModuleRegistry). La décomposition pure garantit la
// sémantique attendue (mass/drag préservés, flag = target) sans couplage ECS.
// These tests target the pure seam buildFlaggedDecision because PhysicsValues static init
// triggers ExceptionInInitializerError outside the Hytale runtime.
@Test
void buildFlaggedDecisionPreservesMassAndDrag() {
@@ -11,15 +11,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Round-trip tests for {@link GravityFlipRegion#CODEC}. Verifies the codec preserves
* the legacy Name + Box + Enabled fields across encode -> decode cycles via the BSON
* intermediate representation, and (Plan 03-04) the 6 optional tuning fields :
* FallDamage, GracePeriodMs, VerticalForce, AffectPlayers, AffectNpcs, AffectItems.
*
* <p>Back-compat invariant (test {@link #roundTripPreservesDefaultsWhenNewFieldsAbsent}) :
* a BSON encoded without the 6 new keys must decode with all Java defaults preserved.
*/
/** Round-trip tests for {@link GravityFlipRegion#CODEC} covering legacy + optional tuning + visualization fields. */
class GravityFlipRegionCodecTest {
@Test
@@ -68,11 +60,9 @@ class GravityFlipRegionCodecTest {
assertEquals("", decoded.getName(), "empty name must survive round-trip without substitution");
}
// ---------- Plan 03-04 : 6 nouveaux champs optionnels ----------
@Test
void roundTripPreservesDefaultsWhenNewFieldsAbsent() {
// Region construite via constructeur legacy 3-arg (comme un regions.json legacy).
// Legacy 3-arg constructor simulates an old regions.json.
GravityFlipRegion src = new GravityFlipRegion(
"legacy",
new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1)),
@@ -80,7 +70,6 @@ class GravityFlipRegionCodecTest {
GravityFlipRegion decoded = roundTrip(src);
// Tous les 6 nouveaux champs doivent exposer leurs defaults Java.
assertFalse(decoded.isFallDamage(), "default FallDamage=false");
assertEquals(2500, decoded.getGracePeriodMs(), "default GracePeriodMs=2500");
assertEquals(0.1, decoded.getVerticalForce(), 1e-9, "default VerticalForce=0.1");
@@ -119,7 +108,6 @@ class GravityFlipRegionCodecTest {
src.setAffectPlayers(false);
GravityFlipRegion decoded = roundTrip(src);
assertFalse(decoded.isAffectPlayers());
// Les autres filtres restent à true (non-clobber).
assertTrue(decoded.isAffectNpcs());
assertTrue(decoded.isAffectItems());
}
@@ -144,8 +132,6 @@ class GravityFlipRegionCodecTest {
assertFalse(decoded.isAffectItems());
}
// ---------- Plan 03-05 : 4 visualization fields ----------
@Test
void roundTripPreservesVisualFields() {
GravityFlipRegion src = baseRegion();
@@ -164,8 +150,6 @@ class GravityFlipRegionCodecTest {
@Test
void roundTripPreservesVisualDefaultsWhenFieldsAbsent() {
// Region construite via constructeur legacy 3-arg — simule un regions.json legacy
// (ni 03-04 ni 03-05 présents).
GravityFlipRegion src = new GravityFlipRegion(
"legacy-viz",
new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1)),
@@ -18,14 +18,8 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
/**
* Pure-math + concurrency tests for {@link RegionRegistry}.
*
* <p>JDK 25 + Mockito + Hytale's {@code World} class is a bad combination — Mockito's inline
* MockMaker (the only one that can mock final classes) triggers static init of the supertype
* {@code PluginBase}, which fails outside a real server because {@code HytaleLogger} requires
* the JUL log manager to be set first. Therefore all snapshot tests use the package-private
* {@code publishSnapshotByKey} / {@code currentSnapshotByKey} hooks with {@code Object}
* sentinels, never a real or mocked {@code World}.
* Pure-math + concurrency tests for {@link RegionRegistry}. Snapshot tests use the package-private
* publishSnapshotByKey / currentSnapshotByKey hooks because Mockito cannot mock World under JDK 25.
*/
class RegionRegistryTest {
@@ -114,11 +108,9 @@ class RegionRegistryTest {
cfg.getRegions().add(new GravityFlipRegion("a", box(), true));
RegionRegistry reg = new RegionRegistry(cfg);
// Reader captures the immutable list before the swap.
var before = reg.enabled();
assertEquals(1, before.size());
// Mutator swaps via refreshFromConfig.
cfg.getRegions().add(new GravityFlipRegion("b", box(), true));
reg.refreshFromConfig(cfg);
@@ -128,7 +120,6 @@ class RegionRegistryTest {
assertEquals(1, before.size());
}
/** Minimal RegionSnapshot for the publish/read tests; world() is unused (returns null). */
private static final class StubSnapshot implements RegionSnapshot {
private final Map<GravityFlipRegion, Collection<Ref<EntityStore>>> by;
private final long tick;
@@ -9,10 +9,7 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
/**
* Scheduler-timing tests for {@link RegionTickLoop}. Use the {@code Runnable} overload so the
* tests don't wait the 2s production initial delay and don't need a real {@code World}.
*/
/** Scheduler-timing tests for {@link RegionTickLoop} using the Runnable overload. */
class RegionTickLoopTest {
@Test
@@ -32,7 +29,7 @@ class RegionTickLoopTest {
RegionTickLoop loop = new RegionTickLoop(reg, t -> {});
AtomicInteger count = new AtomicInteger();
loop.startWithDelay(0L, (Runnable) count::incrementAndGet);
Thread.sleep(300); // ~3 ticks
Thread.sleep(300);
long t0 = System.nanoTime();
loop.stop();
long elapsedMs = (System.nanoTime() - t0) / 1_000_000;
@@ -52,7 +49,7 @@ class RegionTickLoopTest {
int n = count.incrementAndGet();
if (n == 1) throw new RuntimeException("boom on first tick");
});
Thread.sleep(500); // expect ~5 ticks despite the first throwing
Thread.sleep(500);
loop.stop();
assertTrue(count.get() >= 3, "scheduler died after first throw; count=" + count.get());
assertNotNull(capturedFirst.get(), "errorHandler was not invoked");
@@ -10,23 +10,18 @@ import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests for {@link ParticleEdgeEmitter}. Verifies the 12-edge AABB emission
* contract — no diagonals, no interior points, corner-dedup, density clamping.
*/
/** Tests for {@link ParticleEdgeEmitter} — 12-edge AABB emission, no diagonals, corner dedup, density clamping. */
class ParticleEdgeEmitterTest {
private static final double EPS = 1e-9;
@Test
void unitBox_density1_returnsExactly8Corners() {
// 1x1x1 box, density=1 each edge of length 1 → ceil(1*1)=1 → max(2,1)=2
// points per edge (endpoints only), dedup → 8 corners total.
// 1x1x1 box, density=1 -> each edge of length 1 -> 2 points/edge (endpoints), deduped -> 8 corners.
Box b = new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1));
List<Vector3d> pts = ParticleEdgeEmitter.edgePoints(b, 1.0);
assertEquals(8, pts.size(), "unit box at density=1 should emit exactly 8 corner points");
// All 8 canonical corners present.
Set<String> expected = new HashSet<>();
for (double x : new double[]{0, 1})
for (double y : new double[]{0, 1})
@@ -39,30 +34,26 @@ class ParticleEdgeEmitterTest {
@Test
void largeBox_density1_allPointsOnBoxSurfaceAndOnEdges() {
// 10x10x10 box, density=1 → 11 points per edge (incl. endpoints).
Box b = new Box(new Vector3d(0, 0, 0), new Vector3d(10, 10, 10));
List<Vector3d> pts = ParticleEdgeEmitter.edgePoints(b, 1.0);
// Edge membership: each point must lie on 2 of the 6 box planes
// (i.e. at least 2 of its coords are on {min, max} of their axis).
// Edge membership: each point must lie on at least 2 of the 6 box planes.
for (Vector3d p : pts) {
int onPlane = 0;
if (approx(p.x, 0) || approx(p.x, 10)) onPlane++;
if (approx(p.y, 0) || approx(p.y, 10)) onPlane++;
if (approx(p.z, 0) || approx(p.z, 10)) onPlane++;
assertTrue(onPlane >= 2,
"point " + p + " must be on 2 box planes (edge membership), was on " + onPlane);
"point " + p + " must be on >= 2 box planes (edge membership), was on " + onPlane);
}
// Sanity: no duplicate points (corners must be deduped).
Set<String> keys = new HashSet<>();
for (Vector3d p : pts) {
assertTrue(keys.add(key(p.x, p.y, p.z)),
"duplicate point " + p + " — corners should be dedup'd");
"duplicate point " + p + " — corners should be deduped");
}
// Expected count: ceil(10*1) = 10 points/edge (incl. endpoints) 8 interior/edge.
// Total = 8 corners + 12 edges * 8 interior = 8 + 96 = 104.
// 10 points/edge (incl. endpoints) -> 8 interior/edge. Total = 8 corners + 12 * 8 = 104.
assertEquals(104, pts.size());
}
@@ -70,12 +61,9 @@ class ParticleEdgeEmitterTest {
void density_zeroClampedToMin_density1000ClampedToMax() {
Box b = new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1));
// density=0 → clamp to 0.1 → per-edge ceil(1*0.1)=1 → max(2,1)=2 endpoints only.
List<Vector3d> lo = ParticleEdgeEmitter.edgePoints(b, 0.0);
assertEquals(8, lo.size(), "density=0 should clamp to MIN_DENSITY, yielding 8 corners on unit box");
// density=1000 → clamp to 10 → per-edge ceil(1*10)=10 → 10 total points/edge.
// 8 corners + 12 * 8 interior = 8 + 96 = 104.
List<Vector3d> hi = ParticleEdgeEmitter.edgePoints(b, 1000.0);
assertEquals(104, hi.size(), "density=1000 should clamp to MAX_DENSITY=10");
}
@@ -87,14 +75,11 @@ class ParticleEdgeEmitterTest {
assertEquals(8, pts.size());
}
// ---------- helpers ----------
private static boolean approx(double a, double b) {
return Math.abs(a - b) < EPS;
}
private static String key(double x, double y, double z) {
// Round to 6 decimals to avoid floating-point noise in the dedup check.
return String.format("%.6f,%.6f,%.6f", x, y, z);
}
}
@@ -23,11 +23,7 @@ import java.util.concurrent.atomic.AtomicLong;
import static org.junit.jupiter.api.Assertions.*;
/**
* Tests unitaires pour {@link RegionVisualizer}. Le {@code World} n'est jamais touché
* (pas mockable sous JDK 25) : le {@code WorldExecutor} injecté exécute la lambda
* inline sans ré-entrer dans World, et le {@code DebugEmitter} pousse dans une liste.
*/
/** Unit tests for {@link RegionVisualizer} with injected WorldExecutor/DebugEmitter so World is never touched. */
class RegionVisualizerTest {
private static final class Call {
@@ -38,8 +34,6 @@ class RegionVisualizerTest {
}
}
// ---------- parseColor ----------
@Test
void parseColor_validHex() {
Vector3f c = RegionVisualizer.parseColor("#FF8800");
@@ -59,8 +53,6 @@ class RegionVisualizerTest {
}
}
// ---------- normalizeMode / flagsForMode ----------
@Test
void parseMode_unknown_fallsBackToOutline() {
assertEquals("Outline", RegionVisualizer.normalizeMode("Blah"));
@@ -76,7 +68,6 @@ class RegionVisualizerTest {
assertEquals(DebugUtils.FLAG_NO_WIREFRAME, RegionVisualizer.flagsForMode("Faces"));
assertEquals(DebugUtils.FLAG_NONE, RegionVisualizer.flagsForMode("Both"));
assertEquals(DebugUtils.FLAG_NONE, RegionVisualizer.flagsForMode("Particles"));
// unknown → Outline
assertEquals(DebugUtils.FLAG_NO_SOLID, RegionVisualizer.flagsForMode("xxx"));
}
@@ -85,8 +76,6 @@ class RegionVisualizerTest {
assertEquals("Particles", RegionVisualizer.normalizeMode("Particles"));
}
// ---------- Particles branch ----------
@Test
void visualize_particlesMode_callsParticleEmitterOncePerEdgePoint() {
List<String> particleCalls = new ArrayList<>();
@@ -98,14 +87,12 @@ class RegionVisualizerTest {
(w, r) -> r.run(),
() -> 0L);
// unit box at density=1 → 8 corner points emitted.
GravityFlipRegion r = region("pz", "#FFFFFF", "Particles", 1000, 0.5);
r.setVisualParticleId("Dust_Sparkles_Fine");
r.setVisualParticleDensity(1.0);
viz.visualize(null, snapshotOf(r));
assertEquals(8, particleCalls.size(), "unit box + density=1 8 corner emissions");
// All calls use the requested id (validation falls open in test context).
assertEquals(8, particleCalls.size(), "unit box + density=1 -> 8 corner emissions");
for (String call : particleCalls) {
assertTrue(call.startsWith("Dust_Sparkles_Fine@"), "unexpected call: " + call);
}
@@ -113,31 +100,26 @@ class RegionVisualizerTest {
@Test
void particleDefaults_absentInConstructedRegion() {
// Defaults must match the codec's documented defaults (03-06).
GravityFlipRegion r = new GravityFlipRegion("x",
new Box(new Vector3d(0, 0, 0), new Vector3d(1, 1, 1)), true);
assertEquals("Torch_Fire", r.getVisualParticleId());
assertEquals(0.3, r.getVisualParticleDensity(), 1e-9);
}
// ---------- matrixFromBox ----------
@Test
void matrix_boxNonCubic() {
Box b = new Box(new Vector3d(0, 0, 0), new Vector3d(2, 4, 6));
Matrix4d m = RegionVisualizer.matrixFromBox(b);
double[] d = m.getData();
// column-major : scale en diag [0][5][10], translation en [12][13][14]
// column-major: scale on diagonal [0][5][10], translation on [12][13][14].
assertEquals(2.0, d[0], 1e-9);
assertEquals(4.0, d[5], 1e-9);
assertEquals(6.0, d[10], 1e-9);
assertEquals(1.0, d[12], 1e-9); // center x = 1
assertEquals(2.0, d[13], 1e-9); // center y = 2
assertEquals(3.0, d[14], 1e-9); // center z = 3
assertEquals(1.0, d[12], 1e-9);
assertEquals(2.0, d[13], 1e-9);
assertEquals(3.0, d[14], 1e-9);
}
// ---------- visualize : throttling / modes / skip ----------
@Test
void visualize_throttlingSkipsSecondCallWithinWindow() {
List<Call> calls = new ArrayList<>();
@@ -152,15 +134,15 @@ class RegionVisualizerTest {
RegionSnapshot snap = snapshotOf(r);
viz.visualize(null, snap);
assertEquals(1, calls.size(), "premier tick émet");
assertEquals(1, calls.size(), "first tick emits");
clock.set(1_500L); // +500ms < 1000 refreshMs
clock.set(1_500L);
viz.visualize(null, snap);
assertEquals(1, calls.size(), "deuxième tick throttle");
assertEquals(1, calls.size(), "second tick is throttled");
clock.set(2_100L); // +1100ms >= 1000
clock.set(2_100L);
viz.visualize(null, snap);
assertEquals(2, calls.size(), "troisième tick ré-émet après refreshMs");
assertEquals(2, calls.size(), "third tick re-emits after refreshMs");
}
@Test
@@ -193,7 +175,7 @@ class RegionVisualizerTest {
assertEquals(DebugShape.Cube, c.shape);
assertEquals(DebugUtils.FLAG_NO_WIREFRAME, c.flags);
assertEquals(0.75f, c.opacity, 1e-6);
// TTL = 1000 * 1.2 / 1000 = 1.2s
// TTL = 1000 * 1.2 / 1000 = 1.2s.
assertEquals(1.2f, c.time, 1e-3);
}
@@ -201,14 +183,14 @@ class RegionVisualizerTest {
void visualize_clampsOpacityOutOfRange() {
List<Call> calls = new ArrayList<>();
RegionVisualizer viz = newViz(calls, 0L);
GravityFlipRegion r = region("z1", "#00FF00", "Both", 1000, 2.5); // > 1 → clamp à 1
GravityFlipRegion r = region("z1", "#00FF00", "Both", 1000, 2.5);
viz.visualize(null, snapshotOf(r));
assertEquals(1.0f, calls.get(0).opacity, 1e-6);
}
@Test
void visualize_clampsRefreshFloorBelowMin() {
// refreshMs = 10 < MIN_REFRESH_MS (100) effectif = 100ms
// refreshMs=10 < MIN_REFRESH_MS(100) -> effective = 100ms.
List<Call> calls = new ArrayList<>();
AtomicLong clock = new AtomicLong(0L);
RegionVisualizer viz = new RegionVisualizer(
@@ -218,16 +200,14 @@ class RegionVisualizerTest {
clock::get);
GravityFlipRegion r = region("z1", "#00FF00", "Outline", 10, 0.5);
viz.visualize(null, snapshotOf(r));
clock.set(50L); // 50ms < 100 plancher
clock.set(50L);
viz.visualize(null, snapshotOf(r));
assertEquals(1, calls.size(), "plancher 100ms protège contre flood");
assertEquals(1, calls.size(), "100ms floor protects against flood");
clock.set(150L);
viz.visualize(null, snapshotOf(r));
assertEquals(2, calls.size());
}
// ---------- helpers ----------
private static RegionVisualizer newViz(List<Call> calls, long now) {
AtomicLong clock = new AtomicLong(now);
return new RegionVisualizer(