From d69d12be68db16b81f8e7b5934d0f5b7e472973f Mon Sep 17 00:00:00 2001 From: Algent Date: Fri, 4 Sep 2026 00:28:09 +0200 Subject: [PATCH] test: characterize micro occlusion client --- .../HollowMicroblockClientFunctionalTest.java | 42 +++- src/test/fixtures/README.md | 14 ++ .../ReferenceScalaTMicroOcclusionClient.scala | 49 ++++ ...roOcclusionClientCharacterizationTest.java | 237 ++++++++++++++++++ ...eferenceMicroOcclusionClientBase.class.b64 | 1 + ...erenceScalaTMicroOcclusionClient.class.b64 | 1 + 6 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaTMicroOcclusionClient.scala create mode 100644 src/test/java/codechicken/microblock/TMicroOcclusionClientCharacterizationTest.java create mode 100644 src/test/resources/compat/ReferenceMicroOcclusionClientBase.class.b64 create mode 100644 src/test/resources/compat/ReferenceScalaTMicroOcclusionClient.class.b64 diff --git a/src/functionalTest/java/codechicken/multipart/test/HollowMicroblockClientFunctionalTest.java b/src/functionalTest/java/codechicken/multipart/test/HollowMicroblockClientFunctionalTest.java index 73b1b74..8dd8258 100644 --- a/src/functionalTest/java/codechicken/multipart/test/HollowMicroblockClientFunctionalTest.java +++ b/src/functionalTest/java/codechicken/multipart/test/HollowMicroblockClientFunctionalTest.java @@ -77,6 +77,45 @@ class HollowMicroblockClientFunctionalTest { } } + @Test + void occlusionClientRefreshesClippingAndMasksWithLiveStoneAndGlassNeighbors() { + int stone = MicroMaterialRegistry.materialID("minecraft:stone"); + int glass = MicroMaterialRegistry.materialID("minecraft:glass"); + assertNotEquals(stone, glass); + ClientProbe part = new ClientProbe(stone); + ClientProbe neighbor = new ClientProbe(stone); + SlotTile tile = new SlotTile(); + part.bind(tile); + tile.connector.size = 8; + part.setShape(2, 4); + neighbor.setShape(1, 0); + tile.neighbor = neighbor; + TMicroOcclusionClient$class.recalcBounds(part); + assertEquals(1, part.renderMask()); + assertEquals(0, part.renderBounds().min.y); + Cuboid6 original = part.getBounds().copy(); + neighbor.setShape(4, 0); + part.recalcBounds(); + assertEquals(8 << 8, part.renderMask()); + assertEquals(0.5, part.renderBounds().min.y); + assertBounds(original, part.getBounds()); + neighbor.material_$eq(glass); + TMicroOcclusionClient$class.recalcBounds(part); + assertEquals(0, part.renderMask()); + assertBounds(original, part.renderBounds()); + part.material_$eq(glass); + neighbor.material_$eq(stone); + neighbor.setShape(1, 0); + TMicroOcclusionClient$class.recalcBounds(part); + assertEquals(0.125, part.renderBounds().min.y); + assertEquals(0, part.renderMask()); + tile.neighbor = null; + TMicroOcclusionClient$class.recalcBounds(part); + assertNotSame(part.getBounds(), part.renderBounds()); + assertBounds(original, part.renderBounds()); + assertEquals(0, part.renderMask()); + } + private static void assertBounds(Cuboid6 expected, Cuboid6 actual) { assertArrayEquals( new double[] { expected.min.x, expected.min.y, expected.min.z, expected.max.x, expected.max.y, @@ -106,10 +145,11 @@ class HollowMicroblockClientFunctionalTest { final Connector connector = new Connector(); boolean connected = true; + TMultiPart neighbor; @Override public TMultiPart partMap(int slot) { - return slot == 6 && connected ? connector : null; + return slot == 0 ? neighbor : slot == 6 && connected ? connector : null; } } diff --git a/src/test/fixtures/README.md b/src/test/fixtures/README.md index d3ea775..84e178a 100644 --- a/src/test/fixtures/README.md +++ b/src/test/fixtures/README.md @@ -299,3 +299,17 @@ Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouche | --- | --- | | `ReferenceMicroOcclusionBase` | `f3c7c10095fd19d6e18edcee345c4ea5e22557b07595e5274e46be36d0edcb1b` | | `ReferenceScalaTMicroOcclusion` | `d92b9b9d938e8e85b364193807e878f7eb595c099205a4ded84fb1363e11639d` | + +### `ReferenceScalaTMicroOcclusionClient` + +`scala/codechicken/multipart/compat/ReferenceScalaTMicroOcclusionClient.scala` freezes the client trait's +lifecycle super chain and forwarders. Tests pin argument identity, super/recalc failure order, physical-bounds +copying, clipping/mask replacement and partial state publication on failure. + +Compiled with Scala 2.11.5 under Java 8, `-target:jvm-1.8`, against the untouched dev jar at `f6d1568` +(SHA-256 `6949f0c4afb22b3d68ecc42272ca1f88ac1712497556cf5c32c52b2833eb7161`). Source SHA-256 is `ed90e8f184cc4239c69bb6f700283fb25ef2225a59279eb567fc85195ab0f734`. Do not regenerate these class files against the port. + +| Class | SHA-256 | +| --- | --- | +| `ReferenceMicroOcclusionClientBase` | `017938f169d532f72b0ebcba31a67988b32b9116c2a6dbd558c8da2d1ac8f0bf` | +| `ReferenceScalaTMicroOcclusionClient` | `f966dc8e807327865c5ca1736f323ed33a5885f5be1dc6f1b964fa8bd3fdb21d` | diff --git a/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaTMicroOcclusionClient.scala b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaTMicroOcclusionClient.scala new file mode 100644 index 0000000..4680dc4 --- /dev/null +++ b/src/test/fixtures/scala/codechicken/multipart/compat/ReferenceScalaTMicroOcclusionClient.scala @@ -0,0 +1,49 @@ +package codechicken.multipart.compat + +import codechicken.lib.data.MCDataInput +import codechicken.lib.vec.Cuboid6 +import codechicken.microblock.TMicroOcclusionClient +import codechicken.multipart.TMultiPart + +abstract class ReferenceMicroOcclusionClientBase extends TMultiPart { + var events: java.util.List[String] = new java.util.ArrayList[String]() + var failure: RuntimeException = null + var seenPacket: MCDataInput = null + var seenPart: TMultiPart = null + var afterSuper: Runnable = null + private def predecessor(name: String) { + events.add(name) + if (failure != null) throw failure + if (afterSuper != null) afterSuper.run() + } + override def onAdded() { predecessor("superAdded") } + override def onPartChanged(part: TMultiPart) { + seenPart = part + predecessor("superChanged") + } + override def read(packet: MCDataInput) { + seenPacket = packet + predecessor("superRead") + } +} + +/** Frozen Scala lifecycle predecessors and trait forwarders. */ +class ReferenceScalaTMicroOcclusionClient extends ReferenceMicroOcclusionClientBase with TMicroOcclusionClient { + var bounds: Cuboid6 = new Cuboid6(0, 0, 0, 0.25, 1, 1) + var slot = 4 + var size = 2 + var transparent = false + var runRecalc = true + var recalcFailure: RuntimeException = null + override def getType = "test:micro_occlusion_client" + override def getBounds = { events.add("bounds"); bounds } + override def getSlot = slot + override def getSize = size + override def getMaterial = 0 + override def isTransparent = transparent + override def recalcBounds() { + events.add("recalc") + if (recalcFailure != null) throw recalcFailure + if (runRecalc) super.recalcBounds() + } +} diff --git a/src/test/java/codechicken/microblock/TMicroOcclusionClientCharacterizationTest.java b/src/test/java/codechicken/microblock/TMicroOcclusionClientCharacterizationTest.java new file mode 100644 index 0000000..df7882f --- /dev/null +++ b/src/test/java/codechicken/microblock/TMicroOcclusionClientCharacterizationTest.java @@ -0,0 +1,237 @@ +package codechicken.microblock; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.InputStream; +import java.lang.reflect.Proxy; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Scanner; + +import org.junit.jupiter.api.Test; + +import codechicken.lib.data.MCDataInput; +import codechicken.lib.vec.Cuboid6; +import codechicken.multipart.TMultiPart; +import codechicken.multipart.TileMultipart; + +class TMicroOcclusionClientCharacterizationTest { + + @Test + void frozenLifecycleCallsSuperThenVirtualRecalcWithOriginalArguments() throws Exception { + Fixture f = new Fixture(); + f.set("runRecalc", boolean.class, false); + assertNull(f.client.renderBounds()); + assertEquals(0, f.client.renderMask()); + f.part.onAdded(); + assertEquals(Arrays.asList("superAdded", "recalc"), f.events); + f.events.clear(); + f.part.onPartChanged(f.part); + assertSame(f.part, f.get("seenPart")); + assertEquals(Arrays.asList("superChanged", "recalc"), f.events); + f.events.clear(); + MCDataInput packet = (MCDataInput) Proxy.newProxyInstance( + getClass().getClassLoader(), + new Class[] { MCDataInput.class }, + (proxy, method, args) -> { throw new AssertionError("Unexpected packet read: " + method.getName()); }); + f.part.read(packet); + assertSame(packet, f.get("seenPacket")); + assertEquals(Arrays.asList("superRead", "recalc"), f.events); + f.events.clear(); + f.part.read(null); + assertNull(f.get("seenPacket")); + f.part.onPartChanged(null); + assertNull(f.get("seenPart")); + assertEquals(Arrays.asList("superRead", "recalc", "superChanged", "recalc"), f.events); + } + + @Test + void predecessorFailuresSkipRecalculationAndRecalcFailuresFollowSuper() throws Exception { + Fixture f = new Fixture(); + RuntimeException failure = new IllegalStateException("sentinel"); + f.client.renderBounds_$eq(Cuboid6.full); + f.client.renderMask_$eq(91); + Runnable[] calls = { f.part::onAdded, () -> f.part.onPartChanged(null), () -> f.part.read(null) }; + String[] names = { "superAdded", "superChanged", "superRead" }; + for (int i = 0; i < calls.length; i++) { + f.set("failure", RuntimeException.class, failure); + f.events.clear(); + assertSame(failure, assertThrows(IllegalStateException.class, calls[i]::run)); + assertEquals(Collections.singletonList(names[i]), f.events); + f.set("failure", RuntimeException.class, null); + f.set("recalcFailure", RuntimeException.class, failure); + f.events.clear(); + assertSame(failure, assertThrows(IllegalStateException.class, calls[i]::run)); + assertEquals(Arrays.asList(names[i], "recalc"), f.events); + assertSame(Cuboid6.full, f.client.renderBounds()); + assertEquals(91, f.client.renderMask()); + } + } + + @Test + void lifecycleRecalculatesUsingStatePublishedByItsPredecessor() throws Exception { + Fixture f = new Fixture(); + Cuboid6 changed = new Cuboid6(0.2, 0.3, 0.4, 0.8, 0.9, 1); + f.set("afterSuper", Runnable.class, (Runnable) () -> { + try { + f.set("bounds", Cuboid6.class, changed); + } catch (Exception e) { + throw new AssertionError(e); + } + }); + for (Runnable call : new Runnable[] { f.part::onAdded, () -> f.part.onPartChanged(null), + () -> f.part.read(null) }) { + f.set("bounds", Cuboid6.class, null); + f.client.renderMask_$eq(63); + call.run(); + assertNotSame(changed, f.client.renderBounds()); + assertBounds(changed, f.client.renderBounds()); + assertEquals(0, f.client.renderMask()); + } + } + + @Test + void recalculationPublishesVirtualCopyBeforeTraversalAndReplacesOldMask() throws Exception { + Fixture f = new Fixture(); + Cuboid6 copy = new Cuboid6(0.1, 0.2, 0.3, 0.7, 0.8, 0.9); + f.set("bounds", Cuboid6.class, new Cuboid6(Cuboid6.full) { + + @Override + public Cuboid6 copy() { + f.events.add("copy"); + return copy; + } + }); + f.client.renderMask_$eq(45); + f.tile.onVisit = () -> { + assertSame(copy, f.client.renderBounds()); + assertEquals(45, f.client.renderMask()); + }; + f.client.recalcBounds(); + assertEquals(Arrays.asList("recalc", "bounds", "copy"), f.events); + assertEquals(Arrays.asList(0, 1, 2, 3, 5), f.tile.visited); + assertSame(copy, f.client.renderBounds()); + assertEquals(0, f.client.renderMask()); + } + + @Test + void priorityControlsClippingAndOpaqueMaskAndEveryRecalcStartsFromPhysicalBounds() throws Exception { + Fixture f = new Fixture(); + Fixture neighbor = new Fixture(); + neighbor.set("slot", int.class, 0); + neighbor.set("size", int.class, 1); + neighbor.set("bounds", Cuboid6.class, new Cuboid6(0, 0, 0, 1, 0.125, 1)); + f.tile.parts[0] = neighbor.part; + f.client.recalcBounds(); + assertEquals(1, f.client.renderMask()); + assertEquals(0, f.client.renderBounds().min.y); + neighbor.set("size", int.class, 4); + neighbor.set("bounds", Cuboid6.class, new Cuboid6(0, 0, 0, 1, 0.5, 1)); + Cuboid6 previous = f.client.renderBounds(); + f.client.recalcBounds(); + assertNotSame(previous, f.client.renderBounds()); + assertEquals(0.5, f.client.renderBounds().min.y); + assertEquals(0, f.client.renderMask()); + assertEquals(0, ((Cuboid6) f.get("bounds")).min.y); + f.tile.parts[0] = null; + f.client.recalcBounds(); + assertEquals(0, f.client.renderBounds().min.y); + assertEquals(0, f.client.renderMask()); + } + + @Test + void copyFailuresKeepOldStateAndTraversalFailureKeepsPublishedPartialBoundsAndOldMask() throws Exception { + Fixture f = new Fixture(); + Cuboid6 old = Cuboid6.full.copy(); + f.client.renderBounds_$eq(old); + f.client.renderMask_$eq(37); + f.set("bounds", Cuboid6.class, null); + assertThrows(NullPointerException.class, f.client::recalcBounds); + assertSame(old, f.client.renderBounds()); + RuntimeException failure = new IllegalStateException("copy"); + f.set("bounds", Cuboid6.class, new Cuboid6(Cuboid6.full) { + + @Override + public Cuboid6 copy() { + throw failure; + } + }); + assertSame(failure, assertThrows(IllegalStateException.class, f.client::recalcBounds)); + assertSame(old, f.client.renderBounds()); + assertEquals(37, f.client.renderMask()); + f.set("bounds", Cuboid6.class, old); + Fixture neighbor = new Fixture(); + neighbor.set("slot", int.class, 0); + neighbor.set("size", int.class, 4); + neighbor.set("bounds", Cuboid6.class, new Cuboid6(0, 0, 0, 1, 0.5, 1)); + f.tile.parts[0] = neighbor.part; + f.tile.onVisit = () -> { if (f.tile.visited.size() == 2) throw failure; }; + assertSame(failure, assertThrows(IllegalStateException.class, f.client::recalcBounds)); + assertNotSame(old, f.client.renderBounds()); + assertEquals(0.5, f.client.renderBounds().min.y); + assertEquals(0, old.min.y); + assertEquals(37, f.client.renderMask()); + } + + private static void assertBounds(Cuboid6 expected, Cuboid6 actual) { + assertArrayEquals( + new double[] { expected.min.x, expected.min.y, expected.min.z, expected.max.x, expected.max.y, + expected.max.z }, + new double[] { actual.min.x, actual.min.y, actual.min.z, actual.max.x, actual.max.y, actual.max.z }, + 1e-12); + } + + private static final class SlotTile extends TileMultipart { + + final TMultiPart[] parts = new TMultiPart[27]; + final List visited = new ArrayList<>(); + Runnable onVisit; + + @Override + public TMultiPart partMap(int slot) { + visited.add(slot); + if (onVisit != null) onVisit.run(); + return parts[slot]; + } + } + + private static final class Fixture extends ClassLoader { + + final TMultiPart part; + final TMicroOcclusionClient client; + final List events = new ArrayList<>(); + final SlotTile tile = new SlotTile(); + + Fixture() throws Exception { + super(TMultiPart.class.getClassLoader()); + for (String name : new String[] { "ReferenceMicroOcclusionClientBase", + "ReferenceScalaTMicroOcclusionClient" }) { + InputStream input = Objects + .requireNonNull(getClass().getResourceAsStream("/compat/" + name + ".class.b64")); + byte[] bytes; + try (Scanner scanner = new Scanner(input, StandardCharsets.US_ASCII.name()).useDelimiter("\\A")) { + bytes = Base64.getMimeDecoder().decode(scanner.next()); + } + defineClass(null, bytes, 0, bytes.length); + } + part = (TMultiPart) loadClass("codechicken.multipart.compat.ReferenceScalaTMicroOcclusionClient") + .getConstructor().newInstance(); + client = (TMicroOcclusionClient) part; + set("events", List.class, events); + part.bind(tile); + } + + void set(String name, Class type, Object value) throws Exception { + part.getClass().getMethod(name + "_$eq", type).invoke(part, value); + } + + Object get(String name) throws Exception { + return part.getClass().getMethod(name).invoke(part); + } + } +} diff --git a/src/test/resources/compat/ReferenceMicroOcclusionClientBase.class.b64 b/src/test/resources/compat/ReferenceMicroOcclusionClientBase.class.b64 new file mode 100644 index 0000000..45fdc0d --- /dev/null +++ b/src/test/resources/compat/ReferenceMicroOcclusionClientBase.class.b64 @@ -0,0 +1 @@ +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ja2VuL2xpYi9kYXRhL01DRGF0YUlucHV0OylWAQAkKClMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RNdWx0aVBhcnQ7DAAQABEJAAIAJwEADHNlZW5QYXJ0XyRlcQEAJShMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RNdWx0aVBhcnQ7KVYBABYoKUxqYXZhL2xhbmcvUnVubmFibGU7DAASABMJAAIALAEADmFmdGVyU3VwZXJfJGVxAQAXKExqYXZhL2xhbmcvUnVubmFibGU7KVYBAAtwcmVkZWNlc3NvcgEAFShMamF2YS9sYW5nL1N0cmluZzspVgwACQAUCgACADIBAA5qYXZhL3V0aWwvTGlzdAcANAEAA2FkZAEAFShMamF2YS9sYW5nL09iamVjdDspWgwANgA3CwA1ADgMAAwAHAoAAgA6DAASACsKAAIAPAEAEmphdmEvbGFuZy9SdW5uYWJsZQcAPgEAA3J1bgEAAygpVgwAQABBCwA/AEIBAARuYW1lAQASTGphdmEvbGFuZy9TdHJpbmc7AQAHb25BZGRlZAEACnN1cGVyQWRkZWQIAEcMADAAMQoAAgBJAQANb25QYXJ0Q2hhbmdlZAwAKQAqCgACAEwBAAxzdXBlckNoYW5nZWQIAE4BAARwYXJ0AQAEcmVhZAwAJAAlCgACAFIBAAlzdXBlclJlYWQIAFQBAAZwYWNrZXQBAAY8aW5pdD4MAFcAQQoABABYAQATamF2YS91dGlsL0FycmF5TGlzdAcAWgoAWwBYAQAJU2lnbmF0dXJlAQAEQ29kZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAD0xpbmVOdW1iZXJUYWJsZQEAJigpTGphdmEvdXRpbC9MaXN0PExqYXZhL2xhbmcvU3RyaW5nOz47AQAnKExqYXZhL3V0aWwvTGlzdDxMamF2YS9sYW5nL1N0cmluZzs+OylWAQANU3RhY2tNYXBUYWJsZQEAClNvdXJjZUZpbGUBABlSdW50aW1lVmlzaWJsZUFubm90YXRpb25zAQAIU2NhbGFTaWcEIQACAAQAAAAFAAIACQAKAAEAXQAAAAIACwACAAwADQAAAAIADgAPAAAAAgAQABEAAAACABIAEwAAAA8AAQAJABQAAgBeAAAALwABAAEAAAAFKrQAFrAAAAACAF8AAAAMAAEAAAAFABcAGAAAAGAAAAAGAAEAAAAJAF0AAAACAGEAAQAZABoAAgBeAAAAOgACAAIAAAAGKiu1ABaxAAAAAgBfAAAAFgACAAAABgAXABgAAAAAAAYAGwAKAAEAYAAAAAYAAQAAAAkAXQAAAAIAYgABAAwAHAABAF4AAAAvAAEAAQAAAAUqtAAesAAAAAIAXwAAAAwAAQAAAAUAFwAYAAAAYAAAAAYAAQAAAAoAAQAfACAAAQBeAAAAOgACAAIAAAAGKiu1AB6xAAAAAgBfAAAAFgACAAAABgAXABgAAAAAAAYAGwANAAEAYAAAAAYAAQAAAAoAAQAOACEAAQBeAAAALwABAAEAAAAFKrQAI7AAAAACAF8AAAAMAAEAAAAFABcAGAAAAGAAAAAGAAEAAAALAAEAJAAlAAEAXgAAADoAAgACAAAABiortQAjsQAAAAIAXwAAABYAAgAAAAYAFwAYAAAAAAAGABsADwABAGAAAAAGAAEAAAALAAEAEAAmAAEAXgAAAC8AAQABAAAABSq0ACiwAAAAAgBfAAAADAABAAAABQAXABgAAABgAAAABgABAAAADAABACkAKgABAF4AAAA6AAIAAgAAAAYqK7UAKLEAAAACAF8AAAAWAAIAAAAGABcAGAAAAAAABgAbABEAAQBgAAAABgABAAAADAABABIAKwABAF4AAAAvAAEAAQAAAAUqtAAtsAAAAAIAXwAAAAwAAQAAAAUAFwAYAAAAYAAAAAYAAQAAAA0AAQAuAC8AAQBeAAAAOgACAAIAAAAGKiu1AC2xAAAAAgBfAAAAFgACAAAABgAXABgAAAAAAAYAGwATAAEAYAAAAAYAAQAAAA0AAgAwADEAAQBeAAAAdgACAAIAAAAoKrYAMyu5ADkCAFcqtgA7xwAUKrYAPcYADCq2AD25AEMBALEqtgA7vwAAAAMAXwAAABYAAgAAACgAFwAYAAAAAAAoAEQARQABAGAAAAAWAAUAAAAPAAsAEAASABEAIgAOACMAEABjAAAABAACIgAAAQBGAEEAAQBeAAAAMQACAAEAAAAHKhJItwBKsQAAAAIAXwAAAAwAAQAAAAcAFwAYAAAAYAAAAAYAAQAAABMAAQBLACoAAQBeAAAARAACAAIAAAAMKiu2AE0qEk+3AEqxAAAAAgBfAAAAFgACAAAADAAXABgAAAAAAAwAUAARAAEAYAAAAAoAAgAAABUABQAWAAEAUQAlAAEAXgAAAEQAAgACAAAADCortgBTKhJVtwBKsQAAAAIAXwAAABYAAgAAAAwAFwAYAAAAAAAMAFYADwABAGAAAAAKAAIAAAAZAAUAGgABAFcAQQABAF4AAABiAAMAAQAAACQqtwBZKrsAW1m3AFy1ABYqAbUAHioBtQAjKgG1ACgqAbUALbEAAAACAF8AAAAMAAEAAAAkABcAGAAAAGAAAAAaAAYAAAAIAAQACQAPAAoAFAALABkADAAeAA0AAwBkAAAAAgAFAGUAAAALAAEABgABAAdzAAgAZgAAAAMFAAA= diff --git a/src/test/resources/compat/ReferenceScalaTMicroOcclusionClient.class.b64 b/src/test/resources/compat/ReferenceScalaTMicroOcclusionClient.class.b64 new file mode 100644 index 0000000..7fe7a79 --- /dev/null +++ b/src/test/resources/compat/ReferenceScalaTMicroOcclusionClient.class.b64 @@ -0,0 +1 @@ +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JAAIAIAEADnJlbmRlck1hc2tfJGVxAQAEKEkpVgEAQWNvZGVjaGlja2VuJG1pY3JvYmxvY2skVE1pY3JvT2NjbHVzaW9uQ2xpZW50JCRzdXBlciRvblBhcnRDaGFuZ2VkAQAlKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRpUGFydDspVgEADW9uUGFydENoYW5nZWQMACYAJQoABAAnAQAEcGFydAEAIkxjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRpUGFydDsBADtjb2RlY2hpY2tlbiRtaWNyb2Jsb2NrJFRNaWNyb09jY2x1c2lvbkNsaWVudCQkc3VwZXIkb25BZGRlZAEAAygpVgEAB29uQWRkZWQMAC0ALAoABAAuAQA4Y29kZWNoaWNrZW4kbWljcm9ibG9jayRUTWljcm9PY2NsdXNpb25DbGllbnQkJHN1cGVyJHJlYWQBACUoTGNvZGVjaGlja2VuL2xpYi9kYXRhL01DRGF0YUlucHV0OylWAQAEcmVhZAwAMgAxCgAEADMBAAZwYWNrZXQBACJMY29kZWNoaWNrZW4vbGliL2RhdGEvTUNEYXRhSW5wdXQ7AQAyY29kZWNoaWNrZW4vbWljcm9ibG9jay9UTWljcm9PY2NsdXNpb25DbGllbnQkY2xhc3MHADcBAFMoTGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uQ2xpZW50O0xjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRpUGFydDspVgwAJgA5CgA4ADoBADEoTGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uQ2xpZW50OylWDAAtADwKADgAPQEAUyhMY29kZWNoaWNrZW4vbWljcm9ibG9jay9UTWljcm9PY2NsdXNpb25DbGllbnQ7TGNvZGVjaGlja2VuL2xpYi9kYXRhL01DRGF0YUlucHV0OylWDAAyAD8KADgAQAEAEGdldFByaW9yaXR5Q2xhc3MBADEoTGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uQ2xpZW50OylJDABCAEMKADgARAEAO2NvZGVjaGlja2VuJG1pY3JvYmxvY2skVE1pY3JvT2NjbHVzaW9uJCRzdXBlciRvY2NsdXNpb25UZXN0AQAlKExjb2RlY2hpY2tlbi9tdWx0aXBhcnQvVE11bHRpUGFydDspWgEAIGNvZGVjaGlja2VuL211bHRpcGFydC9UTXVsdGlQYXJ0BwBIAQANb2NjbHVzaW9uVGVzdAwASgBHCgBJAEsBAAVucGFydAEALGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uJGNsYXNzBwBOAQBNKExjb2RlY2hpY2tlbi9taWNyb2Jsb2NrL1RNaWNyb09jY2x1c2lvbjtMY29kZWNoaWNrZW4vbXVsdGlwYXJ0L1RNdWx0aVBhcnQ7KVoMAEoAUAoATwBRAQAXZWRnZUNvcm5lck9jY2x1c2lvblRlc3QBAFMoTGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uO0xjb2RlY2hpY2tlbi9taWNyb2Jsb2NrL1RNaWNyb09jY2x1c2lvbjspWgEAeyhMY29kZWNoaWNrZW4vbWljcm9ibG9jay9UTWljcm9PY2NsdXNpb247TGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uO0xjb2RlY2hpY2tlbi9taWNyb2Jsb2NrL1RNaWNyb09jY2x1c2lvbjspWgwAUwBVCgBPAFYBAARlZGdlAQAoTGNvZGVjaGlja2VuL21pY3JvYmxvY2svVE1pY3JvT2NjbHVzaW9uOwEABmNvcm5lcgwACwAMCQACAFsBAApib3VuZHNfJGVxDAANAA4JAAIAXgEACHNsb3RfJGVxDAAPAA4JAAIAYQEACHNpemVfJGVxAQADKClaDAAQABEJAAIAZQEAD3RyYW5zcGFyZW50XyRlcQEABChaKVYMABIAEQkAAgBpAQANcnVuUmVjYWxjXyRlcQEAHigpTGphdmEvbGFuZy9SdW50aW1lRXhjZXB0aW9uOwwAEwAUCQACAG0BABFyZWNhbGNGYWlsdXJlXyRlcQEAHyhMamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb247KVYBAAdnZXRUeXBlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBABt0ZXN0Om1pY3JvX29jY2x1c2lvbl9jbGllbnQIAHMBAAlnZXRCb3VuZHMBAAZldmVudHMBABIoKUxqYXZhL3V0aWw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