feat(03-01): add GravityApplier with native PhysicsValues.invertedGravity toggle (Task 1)
- Service GravityApplier qui toggle PhysicsValues.invertedGravity via CommandBuffer.replaceComponent - Thread-safe tracker ConcurrentHashMap.newKeySet<UUID> pour dedup entre ticks - Second pass O(N) pour restaurer la gravité à la sortie de région (trade-off v1 documenté) - Pure diff static helper + hooks package-private pour tests unitaires sans runtime Hytale - 6 tests verts (diff + tracker semantics)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
package com.mythlane.gravityflip.physics;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
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}.
|
||||
*/
|
||||
class GravityApplierDiffTest {
|
||||
|
||||
@Test
|
||||
void diffComputesToFlipAndToRestore() {
|
||||
UUID a = UUID.randomUUID(), b = UUID.randomUUID(), c = UUID.randomUUID(), d = UUID.randomUUID();
|
||||
Set<UUID> previous = new HashSet<>(Set.of(a, b, c));
|
||||
Set<UUID> current = new HashSet<>(Set.of(b, c, d));
|
||||
GravityApplier.DiffResult res = GravityApplier.diff(previous, current);
|
||||
assertEquals(Set.of(d), res.toFlip);
|
||||
assertEquals(Set.of(a), res.toRestore);
|
||||
}
|
||||
|
||||
@Test
|
||||
void diffEmptyPreviousFlipsAll() {
|
||||
UUID x = UUID.randomUUID(), y = UUID.randomUUID();
|
||||
GravityApplier.DiffResult res = GravityApplier.diff(new HashSet<>(), new HashSet<>(Set.of(x, y)));
|
||||
assertEquals(Set.of(x, y), res.toFlip);
|
||||
assertTrue(res.toRestore.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void diffEmptyCurrentRestoresAll() {
|
||||
UUID x = UUID.randomUUID(), y = UUID.randomUUID();
|
||||
GravityApplier.DiffResult res = GravityApplier.diff(new HashSet<>(Set.of(x, y)), new HashSet<>());
|
||||
assertTrue(res.toFlip.isEmpty());
|
||||
assertEquals(Set.of(x, y), res.toRestore);
|
||||
}
|
||||
|
||||
@Test
|
||||
void diffNoChange() {
|
||||
UUID a = UUID.randomUUID();
|
||||
GravityApplier.DiffResult res = GravityApplier.diff(
|
||||
new HashSet<>(Set.of(a)), new HashSet<>(Set.of(a)));
|
||||
assertTrue(res.toFlip.isEmpty());
|
||||
assertTrue(res.toRestore.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void trackerInitiallyEmpty() {
|
||||
GravityApplier applier = new GravityApplier(t -> {});
|
||||
assertTrue(applier.previouslyInvertedView().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void trackerEvolvesViaPackagePrivateUpdate() {
|
||||
GravityApplier applier = new GravityApplier(t -> {});
|
||||
UUID a = UUID.randomUUID(), b = UUID.randomUUID(), c = UUID.randomUUID();
|
||||
|
||||
applier.__updateTrackerForTest(new HashSet<>(Set.of(a, b)));
|
||||
assertEquals(Set.of(a, b), applier.previouslyInvertedView());
|
||||
|
||||
applier.__updateTrackerForTest(new HashSet<>());
|
||||
assertTrue(applier.previouslyInvertedView().isEmpty());
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user