Document and test preview compatibility (#10)
Build / build (push) Canceled after 0s

This commit is contained in:
dnorberg committed 2026-09-23 02:44:02 -04:00
1 parent 109acc449a
commit 24b60706b0
7 files changed
+166 -26

No files matched your search

+3
View File
@@ -77,6 +77,9 @@ view. Without either integration, Rivencraft uses its built-in animated linking
panel. Clients can set `book_preview.mode` in panel. Clients can set `book_preview.mode` in
`rivencraft-client.toml` to `AUTO`, `IMMERSIVE_PORTALS`, `PONDER`, or `LEGACY`. `rivencraft-client.toml` to `AUTO`, `IMMERSIVE_PORTALS`, `PONDER`, or `LEGACY`.
Unavailable optional renderers safely fall back to `LEGACY`. Unavailable optional renderers safely fall back to `LEGACY`.
The complete backend matrix, supported combinations, cleanup behavior, and
known upstream limits are documented in
[docs/PREVIEW_COMPATIBILITY.md](docs/PREVIEW_COMPATIBILITY.md).
## Build and run ## Build and run
+3 -1
View File
@@ -53,7 +53,9 @@ documented in [AGE_PROFILES.md](AGE_PROFILES.md).
The core mod must remain usable without Create, Ponder, or Immersive Portals. The core mod must remain usable without Create, Ponder, or Immersive Portals.
The built-in legacy animation is the dependency-free destination preview. The built-in legacy animation is the dependency-free destination preview.
Optional preview modes may use Create/Ponder snapshots or Immersive Portals Optional preview modes may use Create/Ponder snapshots or Immersive Portals
live rendering when their dependencies are installed. live rendering when their dependencies are installed. The tested selection
matrix and integration boundaries are documented in
[PREVIEW_COMPATIBILITY.md](PREVIEW_COMPATIBILITY.md).
The writing system and Age grammar will build on the existing page-free Age The writing system and Age grammar will build on the existing page-free Age
path. Page-free books remain a useful deterministic fallback and test fixture path. Page-free books remain a useful deterministic fallback and test fixture
+78
View File
@@ -0,0 +1,78 @@
# Destination preview compatibility
Rivencraft has three destination preview backends. The selected backend is a
client presentation detail; the server validates travel and Age generation is
independent of every preview integration.
## Selection matrix
Set `book_preview.mode` in `rivencraft-client.toml`.
| Configured mode | Immersive Portals installed | Create/Ponder installed | Selected backend |
| --- | --- | --- | --- |
| `AUTO` | yes | either | Immersive Portals |
| `AUTO` | no | yes | Ponder snapshot |
| `AUTO` | no | no | built-in legacy animation |
| `IMMERSIVE_PORTALS` | yes | either | Immersive Portals |
| `IMMERSIVE_PORTALS` | no | either | built-in legacy animation |
| `PONDER` | either | yes | Ponder snapshot |
| `PONDER` | either | no | built-in legacy animation |
| `LEGACY` | either | either | built-in legacy animation |
Explicit optional modes fall back directly to `LEGACY`. They do not silently
select the other optional integration. GameTests exercise every row and every
dependency-presence permutation.
## Book behavior
Held Linking and Descriptive Books render the selected backend in the preview
panel. Immersive Portals additionally exposes **Explore**: the player remains
at the opening position, mouse look controls the remote camera, and
right-click returns to the standard book view.
Books on a Book Stand use the same preview panel, but Explore is disabled so
the player cannot move away and leave a remote preview session owned by the
stand screen. Closing either screen releases its server chunk loader. Linking,
disconnecting, unloading the destination Age, and stopping the server also
release affected loaders.
The Immersive Portals backend force-loads the initial 3 by 3 destination area
before registering its wider preview chunk loader. The Ponder backend clears
the previous snapshot before applying replacement block data. The live backend
clears its framebuffer on each render and restores the Minecraft viewport
afterward. These rules prevent stale snapshot frames and book UI elements from
being captured in the preview.
## Supported combinations and upstream limits
The compatibility baseline is Minecraft 1.21.1, NeoForge 21.1.251, Create
6.0.10, and Immersive Portals 6.0.7. Core Age generation and the `LEGACY`
backend run without Create or Immersive Portals; the headless GameTest server
uses that dependency-free path.
Immersive Portals 6.0.7 is incompatible with Sodium 0.8.13 for Minecraft
1.21.1: its Sodium compatibility mixin references the removed
`OcclusionCuller.Visitor` class and crashes while entering a world. Sodium
0.6.13 avoids that specific mixin failure. This compatibility boundary belongs
to Immersive Portals and Sodium, before Rivencraft's preview code runs.
Immersive Portals reports that some Create contraptions can be invisible when
the two mods are installed together. Rivencraft does not replace either mod's
world or contraption renderer, so this remains an upstream rendering limit.
The `PONDER` or `LEGACY` mode is the supported fallback when live rendering is
unstable in a particular graphics or mod configuration.
## Release verification
For preview-related releases:
1. Run `./gradlew runGameTestServer` to verify the complete selection matrix,
core Age creation, persistence, and travel without optional renderers.
2. Open held Linking and Descriptive Books once with each available backend.
3. Open both book types from a Book Stand and confirm Explore is disabled.
4. With Immersive Portals, enter and leave Explore, travel between dimensions,
close the screen, and disconnect; confirm destination chunks render and no
preview loader remains active.
5. With Ponder, reopen different destinations and confirm the previous
snapshot does not bleed into the next one.
+1 -1
View File
@@ -35,7 +35,7 @@ mod_name=Rivencraft
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. # 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 mod_license=LGPL-3.0-or-later
# The mod version. Rivencraft uses the x.y.z.b policy documented in README.md. # The mod version. Rivencraft uses the x.y.z.b policy documented in README.md.
mod_version=0.2.28.0 mod_version=0.2.29.0
immersive_portals_version=6.0.7 immersive_portals_version=6.0.7
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # 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. # This should match the base package used for the mod sources.
@@ -1,44 +1,22 @@
package net.dnorberg.rivencraft.client.preview; package net.dnorberg.rivencraft.client.preview;
import net.dnorberg.rivencraft.config.RivencraftClientConfig; import net.dnorberg.rivencraft.config.RivencraftClientConfig;
import net.dnorberg.rivencraft.compat.PreviewBackendSelector;
import net.neoforged.fml.ModList; import net.neoforged.fml.ModList;
/** Selects the configured destination preview while keeping optional integrations optional. */ /** Selects the configured destination preview while keeping optional integrations optional. */
public final class DestinationPreviewRenderers { public final class DestinationPreviewRenderers {
private enum Backend {
IMMERSIVE_PORTALS,
PONDER,
LEGACY
}
private DestinationPreviewRenderers() { private DestinationPreviewRenderers() {
} }
public static DestinationPreviewRenderer create() { public static DestinationPreviewRenderer create() {
boolean ponderAvailable = ModList.get().isLoaded("create"); boolean ponderAvailable = ModList.get().isLoaded("create");
// The mode and priority are established before the optional IP renderer
// lands, so selecting it currently follows the documented safe fallback.
boolean immersivePortalsAvailable = ModList.get().isLoaded("immersive_portals_core"); boolean immersivePortalsAvailable = ModList.get().isLoaded("immersive_portals_core");
return switch (selectBackend( return switch (PreviewBackendSelector.select(
RivencraftClientConfig.previewMode(), immersivePortalsAvailable, ponderAvailable)) { RivencraftClientConfig.previewMode(), immersivePortalsAvailable, ponderAvailable)) {
case IMMERSIVE_PORTALS -> new ImmersivePortalsDestinationPreviewRenderer(); case IMMERSIVE_PORTALS -> new ImmersivePortalsDestinationPreviewRenderer();
case PONDER -> new PonderDestinationPreviewRenderer(); case PONDER -> new PonderDestinationPreviewRenderer();
case LEGACY -> new LegacyDestinationPreviewRenderer(); case LEGACY -> new LegacyDestinationPreviewRenderer();
}; };
} }
private static Backend selectBackend(
RivencraftClientConfig.PreviewMode mode,
boolean immersivePortalsAvailable,
boolean ponderAvailable
) {
return switch (mode) {
case AUTO -> immersivePortalsAvailable
? Backend.IMMERSIVE_PORTALS
: ponderAvailable ? Backend.PONDER : Backend.LEGACY;
case IMMERSIVE_PORTALS -> immersivePortalsAvailable ? Backend.IMMERSIVE_PORTALS : Backend.LEGACY;
case PONDER -> ponderAvailable ? Backend.PONDER : Backend.LEGACY;
case LEGACY -> Backend.LEGACY;
};
}
} }
@@ -0,0 +1,30 @@
package net.dnorberg.rivencraft.compat;
import net.dnorberg.rivencraft.config.RivencraftClientConfig;
/** Pure selection policy for optional destination preview integrations. */
public final class PreviewBackendSelector {
public enum Backend {
IMMERSIVE_PORTALS,
PONDER,
LEGACY
}
private PreviewBackendSelector() {
}
public static Backend select(
RivencraftClientConfig.PreviewMode mode,
boolean immersivePortalsAvailable,
boolean ponderAvailable
) {
return switch (mode) {
case AUTO -> immersivePortalsAvailable
? Backend.IMMERSIVE_PORTALS
: ponderAvailable ? Backend.PONDER : Backend.LEGACY;
case IMMERSIVE_PORTALS -> immersivePortalsAvailable ? Backend.IMMERSIVE_PORTALS : Backend.LEGACY;
case PONDER -> ponderAvailable ? Backend.PONDER : Backend.LEGACY;
case LEGACY -> Backend.LEGACY;
};
}
}
@@ -12,6 +12,8 @@ import net.dnorberg.rivencraft.book.LinkTarget;
import net.dnorberg.rivencraft.book.LinkingService; import net.dnorberg.rivencraft.book.LinkingService;
import net.dnorberg.rivencraft.book.AgeVisitSavedData; import net.dnorberg.rivencraft.book.AgeVisitSavedData;
import net.dnorberg.rivencraft.book.AgeTraits; import net.dnorberg.rivencraft.book.AgeTraits;
import net.dnorberg.rivencraft.compat.PreviewBackendSelector;
import net.dnorberg.rivencraft.config.RivencraftClientConfig;
import net.dnorberg.rivencraft.item.DescriptiveBookItem; import net.dnorberg.rivencraft.item.DescriptiveBookItem;
import net.dnorberg.rivencraft.item.LinkingBookItem; import net.dnorberg.rivencraft.item.LinkingBookItem;
import net.dnorberg.rivencraft.registry.ModDataComponents; import net.dnorberg.rivencraft.registry.ModDataComponents;
@@ -40,6 +42,53 @@ public final class LinkingGameTests {
private LinkingGameTests() { private LinkingGameTests() {
} }
@GameTest(template = "empty")
public static void previewBackendSelectionCoversOptionalDependencyMatrix(GameTestHelper helper) {
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.AUTO, true, true,
PreviewBackendSelector.Backend.IMMERSIVE_PORTALS);
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.AUTO, true, false,
PreviewBackendSelector.Backend.IMMERSIVE_PORTALS);
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.AUTO, false, true,
PreviewBackendSelector.Backend.PONDER);
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.AUTO, false, false,
PreviewBackendSelector.Backend.LEGACY);
for (boolean ponderAvailable : new boolean[] {false, true}) {
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.IMMERSIVE_PORTALS, true,
ponderAvailable, PreviewBackendSelector.Backend.IMMERSIVE_PORTALS);
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.IMMERSIVE_PORTALS, false,
ponderAvailable, PreviewBackendSelector.Backend.LEGACY);
}
for (boolean immersivePortalsAvailable : new boolean[] {false, true}) {
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.PONDER,
immersivePortalsAvailable, true, PreviewBackendSelector.Backend.PONDER);
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.PONDER,
immersivePortalsAvailable, false, PreviewBackendSelector.Backend.LEGACY);
}
for (boolean immersivePortalsAvailable : new boolean[] {false, true}) {
for (boolean ponderAvailable : new boolean[] {false, true}) {
assertPreviewBackend(helper, RivencraftClientConfig.PreviewMode.LEGACY,
immersivePortalsAvailable, ponderAvailable, PreviewBackendSelector.Backend.LEGACY);
}
}
helper.succeed();
}
private static void assertPreviewBackend(
GameTestHelper helper,
RivencraftClientConfig.PreviewMode mode,
boolean immersivePortalsAvailable,
boolean ponderAvailable,
PreviewBackendSelector.Backend expected
) {
helper.assertValueEqual(
PreviewBackendSelector.select(mode, immersivePortalsAvailable, ponderAvailable),
expected,
mode + " selection with Immersive Portals=" + immersivePortalsAvailable
+ " and Ponder=" + ponderAvailable
);
}
@GameTest(template = "empty") @GameTest(template = "empty")
public static void dataPackAgeProfilesResolveRegisteredResources(GameTestHelper helper) { public static void dataPackAgeProfilesResolveRegisteredResources(GameTestHelper helper) {
var registries = helper.getLevel().registryAccess(); var registries = helper.getLevel().registryAccess();