Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Semantic client action requested through a reconnect port. */
public enum ReconnectAction {
NONE,
DISCONNECT,
CONNECT,
ENTER_SKYBLOCK,
RETURN_TO_LOBBY,
WARP_GARDEN,
SHOW_TITLE_SCREEN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Result of requesting the action carried by a reconnect decision. */
public enum ReconnectActionStatus {
NOT_REQUESTED,
ACCEPTED,
REJECTED,
FAILED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Platform connection actions; the adapter owns the configured server target and disconnect text. */
public interface ReconnectConnectionPort {
boolean disconnect();

boolean connect();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package dev.hylfrd.farmhelper.feature.reconnect;

import java.util.Objects;
import java.util.OptionalLong;

/** Immutable result of a start, tick, or manual-cancel request. */
public record ReconnectDecision(
ReconnectOutcome outcome,
ReconnectReason reason,
ReconnectState state,
ReconnectAction action,
ReconnectActionStatus actionStatus,
OptionalLong dueAtMillis,
OptionalLong deadlineAtMillis,
int connectionAttempts
) {
public ReconnectDecision {
Objects.requireNonNull(outcome, "outcome");
Objects.requireNonNull(reason, "reason");
Objects.requireNonNull(state, "state");
Objects.requireNonNull(action, "action");
Objects.requireNonNull(actionStatus, "actionStatus");
Objects.requireNonNull(dueAtMillis, "dueAtMillis");
Objects.requireNonNull(deadlineAtMillis, "deadlineAtMillis");
if (connectionAttempts < 0) {
throw new IllegalArgumentException("connectionAttempts must be non-negative");
}
if (dueAtMillis.isPresent() && dueAtMillis.getAsLong() < 0L) {
throw new IllegalArgumentException("dueAtMillis must be non-negative");
}
if (deadlineAtMillis.isPresent() && deadlineAtMillis.getAsLong() < 0L) {
throw new IllegalArgumentException("deadlineAtMillis must be non-negative");
}
boolean running = outcome == ReconnectOutcome.RUNNING;
if (running != (state != ReconnectState.STOPPED)) {
throw new IllegalArgumentException("only a running decision may have an active state");
}
if (running != (dueAtMillis.isPresent() && deadlineAtMillis.isPresent())) {
throw new IllegalArgumentException("active timing must exist exactly while running");
}
if ((action == ReconnectAction.NONE)
!= (actionStatus == ReconnectActionStatus.NOT_REQUESTED)) {
throw new IllegalArgumentException("action and actionStatus do not agree");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Whether one controller decision is active, terminal, or a rejected request. */
public enum ReconnectOutcome {
IDLE,
RUNNING,
SUCCEEDED,
CANCELLED,
TIMED_OUT,
FAILED,
REJECTED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Delay policy for a bounded reconnect run. */
public record ReconnectPolicy(
long connectionSettleDelayMillis,
long retryDelayMillis,
long travelDelayMillis,
long lobbyReturnDelayMillis,
long timeoutMillis
) {
public static final long UPSTREAM_CONNECTION_SETTLE_DELAY_MILLIS = 7_500L;
public static final long UPSTREAM_RETRY_DELAY_MILLIS = 5_000L;
public static final long UPSTREAM_TRAVEL_DELAY_MILLIS = 5_000L;
public static final long UPSTREAM_LOBBY_RETURN_DELAY_MILLIS = 60_000L;

public ReconnectPolicy {
requireNonNegative(connectionSettleDelayMillis, "connectionSettleDelayMillis");
requireNonNegative(retryDelayMillis, "retryDelayMillis");
requireNonNegative(travelDelayMillis, "travelDelayMillis");
requireNonNegative(lobbyReturnDelayMillis, "lobbyReturnDelayMillis");
if (timeoutMillis <= 0L) {
throw new IllegalArgumentException("timeoutMillis must be positive");
}
}

/** Preserves the pinned upstream delays while requiring an explicit local timeout. */
public static ReconnectPolicy upstreamDurations(long timeoutMillis) {
return new ReconnectPolicy(
UPSTREAM_CONNECTION_SETTLE_DELAY_MILLIS,
UPSTREAM_RETRY_DELAY_MILLIS,
UPSTREAM_TRAVEL_DELAY_MILLIS,
UPSTREAM_LOBBY_RETURN_DELAY_MILLIS,
timeoutMillis);
}

private static void requireNonNegative(long value, String name) {
if (value < 0L) {
throw new IllegalArgumentException(name + " must be non-negative");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Typed explanation for a reconnect decision. */
public enum ReconnectReason {
IDLE,
STARTED,
ALREADY_RUNNING,
DELAY_PENDING,
INITIAL_DISCONNECT_PENDING,
CONNECTION_UNKNOWN,
CONNECT_ATTEMPTED,
CONNECTION_ESTABLISHED,
CONNECTION_LOST,
GAME_STATE_NOT_FRESH,
GAME_STATE_UNKNOWN,
GAME_STATE_CONTRADICTORY,
GAME_STATE_TRANSITIONING,
LOBBY_OBSERVED,
LIMBO_OBSERVED,
OUTSIDE_GARDEN,
GARDEN_REACHED,
MANUAL_CANCEL,
STOP_REQUESTED,
CANCEL_SCREEN_UNAVAILABLE,
SINGLEPLAYER_UNSUPPORTED,
PORT_FAILURE,
DEADLINE_REACHED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Screen action used only after a valid manual cancellation. */
@FunctionalInterface
public interface ReconnectScreenPort {
boolean showTitleScreen();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.hylfrd.farmhelper.feature.reconnect;

import dev.hylfrd.farmhelper.runtime.gamestate.GameStateSnapshot;
import dev.hylfrd.farmhelper.runtime.snapshot.ConnectionSnapshot;
import dev.hylfrd.farmhelper.runtime.snapshot.Observation;
import dev.hylfrd.farmhelper.runtime.snapshot.ScreenSnapshot;

import java.util.Objects;

/** One immutable reconnect observation with an explicit monotonic millisecond reading. */
public record ReconnectSnapshot(
long nowMillis,
Observation<ConnectionSnapshot> connection,
Observation<ScreenSnapshot> screen,
GameStateSnapshot gameState
) {
public ReconnectSnapshot {
if (nowMillis < 0L) {
throw new IllegalArgumentException("nowMillis must be non-negative");
}
Objects.requireNonNull(connection, "connection");
Objects.requireNonNull(screen, "screen");
Objects.requireNonNull(gameState, "gameState");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Upstream AutoReconnect phases, with NONE renamed to an explicit inactive state. */
public enum ReconnectState {
STOPPED,
CONNECTING,
LOBBY,
GARDEN
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dev.hylfrd.farmhelper.feature.reconnect;

/** Semantic server-travel actions used after a connection has been established. */
public interface ReconnectTravelPort {
boolean enterSkyBlock();

boolean returnToLobby();

boolean warpToGarden();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Minecraft-free AutoReconnect state, snapshot inputs, decisions, and semantic action ports.
* Lifecycle composition is intentionally deferred.
*/
package dev.hylfrd.farmhelper.feature.reconnect;
Loading
Loading