From b6f321e6cb0a811b0628ade7c407e6b59fcd6d08 Mon Sep 17 00:00:00 2001 From: Dustin Norberg Date: Mon, 21 Sep 2026 08:32:18 -0400 Subject: [PATCH] Add linking game tests --- .gitignore | 1 + docs/PORTING.md | 7 +- gradle.properties | 2 +- .../rivencraft/gametest/LinkingGameTests.java | 78 ++++++++++++++++++ .../data/rivencraft/structure/empty.nbt | Bin 0 -> 108 bytes 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java create mode 100644 src/main/resources/data/rivencraft/structure/empty.nbt diff --git a/.gitignore b/.gitignore index fee2f7b..f0a29ac 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ bin/ ### Minecraft Modding ### run/ +logs/ !**/src/**/run/ **/src/generated/**/.cache/ repo/ diff --git a/docs/PORTING.md b/docs/PORTING.md index b64974f..310d045 100644 --- a/docs/PORTING.md +++ b/docs/PORTING.md @@ -54,7 +54,7 @@ the symbol grammar is introduced. - [x] Server-provided destination snapshots rendered as rotating Ponder scenes. - [x] Book Stand floating names synchronized from the stored book stack. - [x] Open the stored book screen from a Book Stand with server-validated linking. -- [ ] Automated in-game tests for binding, persistence, and travel. +- [x] Automated in-game tests for binding, persistence, and travel. The temporary shapeless Linking Book recipe makes the linking subsystem easy to test. It can move into the writing/bookbinder progression when that system @@ -118,6 +118,11 @@ Version 0.1.14 expands the destination snapshot to 9-by-7-by-17 blocks along the Z axis, rescales the rotating scene to expose that depth, and colors the panel background with the destination biome's sky color. +Version 0.1.15 adds three dedicated-server GameTests covering Linking Book +component serialization, successful safe travel with restored orientation, and +rejection of fully blocked destinations. The test suite uses a minimal bundled +structure template and runs with `./gradlew runGameTestServer`. + The preview code is separated behind a renderer interface and uses an isolated Ponder level. The first iteration supplies the animated loading boundary; the next iteration will add server-authoritative snapshot transport and populate diff --git a/gradle.properties b/gradle.properties index e462837..25ab37a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -33,7 +33,7 @@ mod_name=Rivencraft # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=LGPL-3.0-or-later # The mod version. See https://semver.org/ -mod_version=0.1.14 +mod_version=0.1.15 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java new file mode 100644 index 0000000..bd37429 --- /dev/null +++ b/src/main/java/net/dnorberg/rivencraft/gametest/LinkingGameTests.java @@ -0,0 +1,78 @@ +package net.dnorberg.rivencraft.gametest; + +import net.dnorberg.rivencraft.Rivencraft; +import net.dnorberg.rivencraft.book.LinkTarget; +import net.dnorberg.rivencraft.book.LinkingService; +import net.dnorberg.rivencraft.registry.ModDataComponents; +import net.dnorberg.rivencraft.registry.ModItems; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.gametest.framework.GameTestHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Blocks; +import net.neoforged.neoforge.gametest.GameTestHolder; +import net.neoforged.neoforge.gametest.PrefixGameTestTemplate; + +/** Server-side regression coverage for persistent linking-book data and travel safety. */ +@GameTestHolder(Rivencraft.MOD_ID) +@PrefixGameTestTemplate(false) +public final class LinkingGameTests { + private static final BlockPos ORIGIN = new BlockPos(0, 1, 0); + + private LinkingGameTests() { + } + + @GameTest(template = "empty") + public static void linkTargetSurvivesItemSerialization(GameTestHelper helper) { + LinkTarget expected = new LinkTarget( + GlobalPos.of(helper.getLevel().dimension(), helper.absolutePos(ORIGIN)), + 127.5F, + -31.25F + ); + ItemStack book = new ItemStack(ModItems.LINKING_BOOK.get()); + book.set(ModDataComponents.LINK_TARGET.get(), expected); + + CompoundTag serialized = (CompoundTag) book.saveOptional(helper.getLevel().registryAccess()); + ItemStack restored = ItemStack.parseOptional(helper.getLevel().registryAccess(), serialized); + + helper.assertValueEqual(restored.get(ModDataComponents.LINK_TARGET.get()), expected, + "A Linking Book must retain its dimension, position, and orientation after serialization"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void travelUsesRecordedSafePosition(GameTestHelper helper) { + helper.setBlock(ORIGIN.below(), Blocks.STONE); + BlockPos destination = helper.absolutePos(ORIGIN); + ServerPlayer player = helper.makeMockServerPlayerInLevel(); + LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), destination), 90.0F, 15.0F); + + LinkingService.LinkResult result = LinkingService.travel(player, target); + + helper.assertValueEqual(result, LinkingService.LinkResult.SUCCESS, + "A Linking Book must travel to a clear location with a sturdy floor"); + helper.assertTrue(player.level() == helper.getLevel(), "Travel must remain in the recorded dimension"); + helper.assertTrue(Math.abs(player.getYRot() - 90.0F) < 0.01F && Math.abs(player.getXRot() - 15.0F) < 0.01F, + "Travel must restore the recorded orientation"); + helper.succeed(); + } + + @GameTest(template = "empty") + public static void travelRejectsBlockedDestination(GameTestHelper helper) { + BlockPos absoluteOrigin = helper.absolutePos(ORIGIN); + for (BlockPos position : BlockPos.betweenClosed( + absoluteOrigin.offset(-4, -2, -4), absoluteOrigin.offset(4, 2, 4))) { + helper.getLevel().setBlockAndUpdate(position, Blocks.STONE.defaultBlockState()); + } + + ServerPlayer player = helper.makeMockServerPlayerInLevel(); + LinkTarget target = new LinkTarget(GlobalPos.of(helper.getLevel().dimension(), absoluteOrigin), 0.0F, 0.0F); + + helper.assertValueEqual(LinkingService.travel(player, target), LinkingService.LinkResult.NO_SAFE_DESTINATION, + "Linking must fail rather than placing a player inside a blocked destination"); + helper.succeed(); + } +} diff --git a/src/main/resources/data/rivencraft/structure/empty.nbt b/src/main/resources/data/rivencraft/structure/empty.nbt new file mode 100644 index 0000000000000000000000000000000000000000..5307dd1d4f947df609b29f29d5f66dd00eecf258 GIT binary patch literal 108 zcmb2|=3oGW|E4{LTnz>SEa4)if*IH7lOnX+dU|L)EyVt$c*96aK3 Lrn)APg+NOHyM`^y literal 0 HcmV?d00001