diff --git a/src/main/java/com/mythlane/chainlightning/chain/VolumeCurve.java b/src/main/java/com/mythlane/chainlightning/chain/VolumeCurve.java new file mode 100644 index 0000000..016eb56 --- /dev/null +++ b/src/main/java/com/mythlane/chainlightning/chain/VolumeCurve.java @@ -0,0 +1,40 @@ +package com.mythlane.chainlightning.chain; + +/** + * Pure-Java volume curve for the chain-lightning sound emission. + *

+ * Each successive hop in the chain emits its sound at a progressively lower + * volume, per VFX-02. The curve is hard-coded to the spec values + * {@code [1.0, 0.8, 0.6, 0.5, 0.4]} indexed by hop number. + *

+ * This class has ZERO Hytale imports — Phase 2 sealed-frontier rule preserved. + */ +public final class VolumeCurve { + + /** Spec curve from VFX-02. Order and values are exact. */ + private static final float[] CURVE = {1.0f, 0.8f, 0.6f, 0.5f, 0.4f}; + + private VolumeCurve() { + // utility class — no instances + } + + /** + * Volume for a given hop index in the chain. + *

+ * + * @param hopIndex zero-based hop index + * @return volume scalar in the range (0, 1] + * @throws IllegalArgumentException if {@code hopIndex < 0} + */ + public static float volumeFor(int hopIndex) { + if (hopIndex < 0) { + throw new IllegalArgumentException("hopIndex must be >= 0, got " + hopIndex); + } + int clamped = Math.min(hopIndex, CURVE.length - 1); + return CURVE[clamped]; + } +} diff --git a/src/test/java/com/mythlane/chainlightning/chain/VolumeCurveTest.java b/src/test/java/com/mythlane/chainlightning/chain/VolumeCurveTest.java new file mode 100644 index 0000000..81fc126 --- /dev/null +++ b/src/test/java/com/mythlane/chainlightning/chain/VolumeCurveTest.java @@ -0,0 +1,31 @@ +package com.mythlane.chainlightning.chain; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +final class VolumeCurveTest { + + @Test + void specValues() { + assertEquals(1.0f, VolumeCurve.volumeFor(0), 1e-6); + assertEquals(0.8f, VolumeCurve.volumeFor(1), 1e-6); + assertEquals(0.6f, VolumeCurve.volumeFor(2), 1e-6); + assertEquals(0.5f, VolumeCurve.volumeFor(3), 1e-6); + assertEquals(0.4f, VolumeCurve.volumeFor(4), 1e-6); + } + + @Test + void indexClamp() { + assertEquals(0.4f, VolumeCurve.volumeFor(5), 1e-6); + assertEquals(0.4f, VolumeCurve.volumeFor(99), 1e-6); + assertEquals(0.4f, VolumeCurve.volumeFor(Integer.MAX_VALUE), 1e-6); + } + + @Test + void negativeIndexThrows() { + assertThrows(IllegalArgumentException.class, () -> VolumeCurve.volumeFor(-1)); + assertThrows(IllegalArgumentException.class, () -> VolumeCurve.volumeFor(Integer.MIN_VALUE)); + } +}