Complete 26.3 parity acceptance and dual-version release workflow
Build and smoke / changes (push) Waiting to run

This commit is contained in:
dnorberg committed 2026-11-14 12:17:49 -05:00
1 parent 41daff1e61
commit 4a59fb5d02
26 files changed
+1241 -369

No files matched your search

+9 -7
View File
@@ -1,6 +1,6 @@
# Minecraft 26.3 beta port workspace
# Minecraft 26.3
The current **Minecraft 26.3 release is 26.3.0.1**, for NeoForge 26.3.0.99 and
The current **Minecraft 26.3 release is 26.3.0.3**, for NeoForge 26.3.0.99 and
Java 25. It uses Rivencraft's first-party Age backend, with no Dynamic Dimensions
or Bad Packets dependency. The writing loop, authored/seeded Ages, generation
features, weather/skies/celestials, libraries, crystal portals, live instability,
@@ -16,11 +16,13 @@ with `book_previews.reborn_live_views` in `rivencraft-local.toml`.
Reborn's Static Sky rendering issue remains tracked in
[#116](https://git.paddedcell.org/dnorberg/rivencraft/issues/116).
This regular release is player-validated but does not claim complete 1.21.1 parity.
Issues #100, #103 and #104 retain the final multiplayer/parity/release-workflow
checks. Preview exploration controls and Create/Ponder scenes remain unported.
Use matching client/server builds, keep saves separate from 1.21.1 and back up
before updating. See [release notes](RELEASE_NOTES_26.3.0.1.md).
The port feature and acceptance review is recorded in
[the parity review](../../docs/MC263_PARITY_REVIEW.md). Book exploration matches
1.21.1: use the eye for mouse look inside the book panel and right-click to return;
movement is disabled. The final dedicated-server restart/return check passed.
Create/Ponder scenes are not ported to this target. Use matching client/server
builds, keep saves separate from 1.21.1 and back up before updating.
See [release notes](RELEASE_NOTES_26.3.0.3.md).
The entries below record how the port was built, including earlier alpha-only
limitations that were addressed by later slices.
+38
View File
@@ -0,0 +1,38 @@
# Rivencraft 26.3.0.3
For **Minecraft 26.3**, **NeoForge 26.3.0.99** and **Java 25**.
## Book preview exploration
- The eye button now matches 1.21.1: it grabs the mouse for look-only exploration
while the destination view remains inside the book panel. Movement is disabled.
- Right-click returns to the ordinary book controls without releasing the preview.
Books on stands retain their disabled exploration button.
- Fixed the exit crash caused by a forced render and duplicate Reborn render-target
submission. Pending target submissions are reused until the renderer drains them.
- Removed remaining Beta branding from the in-game mod name/description.
## Port acceptance and maintenance
- Completed dedicated-server restart/return-link acceptance for #100, following
the two-client matrix and its manual handoff. The player confirmed return travel.
- Added changed-target build/smoke workflows, sequential test-server exclusion and
repeatable dual-version validation commands.
- Hardened release uploads against mismatched filename, embedded version,
Minecraft/Java target and accidental test-fixture packaging. The 1.21.1 metadata
declares its required Dynamic Dimensions AR and Bad Packets dependencies.
- Documented the feature parity review and ongoing independent support of 1.21.1.
Existing 1.21.1 code and published artifacts are unchanged.
## Validation and known issues
The player passed the live-view exploration/right-click/re-entry check and the
dedicated-server Age restart/Linking Book return check. The expanded-catalog
two-client matrix passed creation/catalog/travel/stale-ack/lifecycle checks;
its final return step stalled and was completed by the player, not retried.
Release-tooling checks pass. No optional mod or test fixture is bundled.
**Known issue #116:** Static Sky can leak outside book previews with Reborn.
It remains separately tracked. Other shared follow-ups remain open where needed.
Create/Ponder is not ported to 26.3. Without Reborn, built-in animated previews
and normal book/portal travel remain available. 1.21.1 saves are not imported.
+1 -1
View File
@@ -7,7 +7,7 @@ neo_version=26.3.0.99
mod_id=rivencraft
mod_name=Rivencraft
mod_license=LGPL-3.0-or-later
mod_version=26.3.0.1
mod_version=26.3.0.3
jei_version=31.17.0.109
@@ -0,0 +1,120 @@
package net.dnorberg.rivencraft.client;
import net.dnorberg.rivencraft.Rivencraft;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.client.event.InputEvent;
import net.neoforged.neoforge.client.event.RenderGuiEvent;
import com.mojang.blaze3d.platform.InputConstants;
/** Matches 1.21.1: grabbed mouse look with the book drawn over the source world. */
@EventBusSubscriber(modid = Rivencraft.MOD_ID, value = Dist.CLIENT)
public final class PortBookExploration {
private static PortBookExploration current;
private final PortLegacyLinkPanel panel;
private Screen owner;
private Button eye;
private Vec3 sourcePosition;
private net.minecraft.client.multiplayer.ClientLevel sourceLevel;
private int guiWidth, guiHeight;
private boolean active;
PortBookExploration(PortLegacyLinkPanel panel) { this.panel = panel; }
void attach(Screen screen, java.util.function.Consumer<Button> add, int left, int top, boolean stand) {
owner = screen;
eye = Button.builder(Component.literal("👁"), button -> {
var minecraft = Minecraft.getInstance();
if (stand || !panel.canExplore() || minecraft.player == null) return;
sourcePosition = minecraft.player.position();
sourceLevel = minecraft.level;
guiWidth = minecraft.getWindow().getGuiScaledWidth();
guiHeight = minecraft.getWindow().getGuiScaledHeight();
active = true;
current = this;
eye.visible = false;
panel.exploring(true);
KeyMapping.releaseAll();
// Removing the screen grabs the mouse normally. removed() preserves this session.
minecraft.gui.setScreen(null);
}).bounds(left + 229, top + 145, 20, 20).build();
eye.active = !stand;
eye.setTooltip(Tooltip.create(Component.literal("Look around destination (right-click to return)")));
add.accept(eye);
}
void tick(boolean frontPage) {
if (eye != null) eye.visible = !active && frontPage && panel.canExplore();
}
boolean active() { return active; }
void stop() {
active = false;
if (current == this) current = null;
sourcePosition = null;
sourceLevel = null;
panel.exploring(false);
}
@SubscribeEvent
public static void beforeTick(ClientTickEvent.Pre event) {
if (current != null) KeyMapping.releaseAll();
}
@SubscribeEvent
public static void afterTick(ClientTickEvent.Post event) {
var overlay = current;
if (overlay == null) return;
var minecraft = Minecraft.getInstance();
if (minecraft.player == null || minecraft.level != overlay.sourceLevel || minecraft.gui.screen() != null) {
overlay.stop();
overlay.panel.close();
return;
}
minecraft.player.setPos(overlay.sourcePosition);
minecraft.player.setDeltaMovement(Vec3.ZERO);
overlay.panel.tick();
}
@SubscribeEvent
public static void drawBook(RenderGuiEvent.Post event) {
var overlay = current;
var minecraft = Minecraft.getInstance();
if (overlay == null || minecraft.gui.screen() != null) return;
int width = minecraft.getWindow().getGuiScaledWidth();
int height = minecraft.getWindow().getGuiScaledHeight();
if (width != overlay.guiWidth || height != overlay.guiHeight) {
overlay.guiWidth = width;
overlay.guiHeight = height;
overlay.owner.resize(width, height);
overlay.eye.visible = false;
}
overlay.owner.extractRenderState(event.getGuiGraphics(), -1, -1,
event.getPartialTick().getGameTimeDeltaPartialTick(false));
}
@SubscribeEvent
public static void mouseButton(InputEvent.MouseButton.Pre event) {
var overlay = current;
if (overlay == null || Minecraft.getInstance().gui.screen() != null) return;
// Neither mouse button may use the held book or attack in the source world.
event.setCanceled(true);
if (event.getButton() == InputConstants.MOUSE_BUTTON_RIGHT && event.getAction() == InputConstants.PRESS) {
overlay.stop();
KeyMapping.releaseAll();
// Unlike 1.21.1 setScreen, setScreenAndShow forces a render immediately.
// Resume through the regular GUI lifecycle without nesting a new frame.
Minecraft.getInstance().gui.setScreen(overlay.owner);
}
}
}
@@ -18,6 +18,8 @@ public final class PortBookPreview {
void accept(net.dnorberg.rivencraft.item.LinkBookTarget destination);
boolean render(GuiGraphicsExtractor graphics, int x, int y, int width, int height, float time);
void close();
default void exploring(boolean enabled) {}
default void tick() {}
}
private static PortBookPreview active;
private final UUID session = UUID.randomUUID();
@@ -46,12 +48,16 @@ public final class PortBookPreview {
}
void tick() {
ticks++;
if (live != null) live.tick();
if (ticks % (received ? 200 : 40) == 0) sendRequest();
}
boolean render(GuiGraphicsExtractor graphics, int x, int y, int width, int height, float partialTick) {
if (closed || !received || live == null) return false;
return live.render(graphics, x, y, width, height, ticks + partialTick);
}
boolean canExplore() { return live != null && received && !closed; }
void exploring(boolean enabled) { if (live != null) live.exploring(enabled); }
public static void accept(BookPreviewPayload payload) {
if (active == null || active.closed || active.live == null || !active.session.equals(payload.session())
|| !payload.destination().valid()) return;
@@ -27,6 +27,7 @@ public final class PortDescriptiveBookScreen extends Screen {
private final boolean bound;
private final net.minecraft.resources.Identifier ageId;
private final PortLegacyLinkPanel linkPanel = new PortLegacyLinkPanel();
private final PortBookExploration exploration = new PortBookExploration(linkPanel);
private final net.dnorberg.rivencraft.writing.DescriptiveBookPages pages;
private int pageIndex;
private net.dnorberg.rivencraft.network.UseBookStandPayload standSource;
@@ -48,7 +49,11 @@ public final class PortDescriptiveBookScreen extends Screen {
@Override
protected void init() {
if (bound) { linkPanel.open(hand, book, standSource); return; }
if (bound) {
linkPanel.open(hand, book, standSource);
exploration.attach(this, this::addRenderableWidget, (width - WIDTH) / 2, (height - HEIGHT) / 2, standSource != null);
return;
}
if (standSource != null) return;
int left = (width - WIDTH) / 2;
int top = (height - HEIGHT) / 2;
@@ -183,9 +188,10 @@ public final class PortDescriptiveBookScreen extends Screen {
@Override public void tick() {
if (bound) linkPanel.tick();
exploration.tick(pageIndex == 0);
}
@Override public void removed() { linkPanel.close(); super.removed(); }
@Override public void removed() { if (!exploration.active()) { exploration.stop(); linkPanel.close(); } super.removed(); }
@Override
public boolean isPauseScreen() {
@@ -19,7 +19,9 @@ final class PortLegacyLinkPanel {
net.dnorberg.rivencraft.network.UseBookStandPayload stand) {
if (preview == null) preview = new PortBookPreview(hand, book, stand);
}
void close() { if (preview != null) preview.close(); }
void close() { if (preview != null) { preview.close(); preview = null; } }
boolean canExplore() { return preview != null && preview.canExplore(); }
void exploring(boolean value) { if (preview != null) preview.exploring(value); }
private static final int LINK_COLOR = 0xFF006666;
@@ -30,6 +30,7 @@ public final class PortLinkingBookScreen extends Screen {
private final Component destination;
private int pageIndex;
private final PortLegacyLinkPanel linkPanel = new PortLegacyLinkPanel();
private final PortBookExploration exploration = new PortBookExploration(linkPanel);
private net.dnorberg.rivencraft.network.UseBookStandPayload standSource;
private EditBox titleInput;
@@ -53,7 +54,11 @@ public final class PortLinkingBookScreen extends Screen {
@Override
protected void init() {
if (bound) { linkPanel.open(hand, book, standSource); return; }
if (bound) {
linkPanel.open(hand, book, standSource);
exploration.attach(this, this::addRenderableWidget, (width - WIDTH) / 2, (height - HEIGHT) / 2, standSource != null);
return;
}
if (standSource != null) return;
int left = (width - WIDTH) / 2;
int top = (height - HEIGHT) / 2;
@@ -148,9 +153,10 @@ public final class PortLinkingBookScreen extends Screen {
@Override public void tick() {
if (bound) linkPanel.tick();
exploration.tick(pageIndex == 0);
}
@Override public void removed() { linkPanel.close(); super.removed(); }
@Override public void removed() { if (!exploration.active()) { exploration.stop(); linkPanel.close(); } super.removed(); }
@Override
public boolean isPauseScreen() {
@@ -26,7 +26,28 @@ public final class RebornLiveBookPanel implements PortBookPreview.LivePanel {
private static RebornLiveBookPanel queued;
private LinkBookTarget destination;
private boolean closed;
private boolean exploring;
private Vec3 explorePosition;
private float yaw, pitch, playerYaw, playerPitch;
@Override public void exploring(boolean enabled) {
exploring = enabled;
if (enabled && destination != null) {
explorePosition = new Vec3(destination.x(), destination.y() + 1.62, destination.z());
yaw = destination.yaw(); pitch = destination.pitch();
var player = Minecraft.getInstance().player;
if (player != null) {
explorePosition = new Vec3(destination.x(), destination.y() + player.getEyeHeight(), destination.z());
playerYaw = player.getYRot(); playerPitch = player.getXRot();
}
}
}
@Override public void tick() {
var player = Minecraft.getInstance().player;
if (!exploring || destination == null || player == null) return;
yaw = destination.yaw() + net.minecraft.util.Mth.wrapDegrees(player.getYRot() - playerYaw);
pitch = Math.clamp(destination.pitch() + player.getXRot() - playerPitch, -89, 89);
}
@Override public void accept(LinkBookTarget value) { destination = value; }
@Override public boolean render(GuiGraphicsExtractor graphics, int x, int y, int width, int height, float time) {
if (closed || destination == null || !ClientWorldLoader.getIsInitialized()) return false;
@@ -40,16 +61,25 @@ public final class RebornLiveBookPanel implements PortBookPreview.LivePanel {
Vec3 center = new Vec3(destination.x(), destination.y() + 2.5, destination.z());
double angle = time * 0.012;
Vec3 camera = center.add(Math.sin(angle) * 11, 5, Math.cos(angle) * 11);
Matrix4f transform = new Matrix4f().rotateX((float) Math.toRadians(20))
.rotateY((float) angle);
Matrix4f transform;
if (exploring && explorePosition != null) {
camera = explorePosition;
transform = new Matrix4f().rotateX((float) Math.toRadians(pitch))
.rotateY((float) Math.toRadians(yaw + 180));
} else transform = new Matrix4f().rotateX((float) Math.toRadians(20)).rotateY((float) angle);
var info = new WorldRenderInfo.Builder().setWorld(world).setCameraPos(camera)
.setCameraTransformation(transform).setOverwriteCameraTransformation(true)
.setRenderDistance(Math.min(8, minecraft.options.getEffectiveRenderDistance()))
.setDoRenderHand(false).setEnableViewBobbing(false).setDoRenderSky(true).setHasFog(true).build();
// Submit first: Reborn may resize the target, invalidating its old texture view.
// GPU clearing happens in beginRender(), after extraction but before remote rendering.
GuiPortalRendering.submitNextFrameRendering(info, target);
queued = this;
// Overlay-to-screen transitions can extract this panel twice before Reborn
// drains its queue. Its API accepts only one task per target until then.
// Reuse that pending view for subsequent draws instead of submitting twice.
if (queued == null) {
// Submit first: Reborn may resize the target and invalidate its texture view.
// GPU clearing happens in beginRender(), before remote rendering.
GuiPortalRendering.submitNextFrameRendering(info, target);
queued = this;
}
graphics.blit(target.getColorTextureView(), RenderSystem.getSamplerCache().getClampToEdge(FilterMode.LINEAR),
x, y, x + width, y + height, 0, 1, 1, 0);
graphics.outline(x, y, width, height, 0xFF7B6AA8);
@@ -75,5 +105,5 @@ public final class RebornLiveBookPanel implements PortBookPreview.LivePanel {
camera.update(minecraft.getDeltaTracker());
}
}
@Override public void close() { closed = true; destination = null; }
@Override public void close() { exploring(false); closed = true; destination = null; }
}
@@ -4,11 +4,11 @@ issueTrackerURL="https://git.paddedcell.org/dnorberg/rivencraft/issues"
[[mods]]
modId="${mod_id}"
version="${mod_version}"
displayName="${mod_name} 26.3 Beta"
displayName="${mod_name}"
displayURL="https://git.paddedcell.org/dnorberg/rivencraft"
authors="dnorberg and contributors"
description='''
Experimental Minecraft 26.3 beta with books, writing stations and selected
Minecraft 26.3 release with books, writing stations and selected
authored Age generation. Full 1.21.1 gameplay parity is still in progress.
Use a test world and keep backups; 1.21.1 worlds are not compatible.
'''