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
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package dev.hylfrd.farmhelper.navigation.simulation;

import java.util.Objects;
import java.util.Optional;

/** Read-only world evidence that distinguishes a known value from an unavailable value. */
public record FlyStoppingEvidence<T>(
Optional<T> value,
Optional<FlyStoppingFailure> failure
) {
public FlyStoppingEvidence {
Objects.requireNonNull(value, "value");
Objects.requireNonNull(failure, "failure");
if (value.isPresent() == failure.isPresent()) {
throw new IllegalArgumentException("evidence must contain exactly one payload");
}
if (failure.isPresent() && !failure.get().evidenceFailure()) {
throw new IllegalArgumentException("failure does not describe world evidence");
}
}

public static <T> FlyStoppingEvidence<T> known(T value) {
return new FlyStoppingEvidence<>(
Optional.of(Objects.requireNonNull(value, "value")), Optional.empty());
}

public static <T> FlyStoppingEvidence<T> unknown(FlyStoppingFailure failure) {
Objects.requireNonNull(failure, "failure");
if (!failure.evidenceFailure()) {
throw new IllegalArgumentException("failure does not describe world evidence");
}
return new FlyStoppingEvidence<>(Optional.empty(), Optional.of(failure));
}

public boolean isKnown() {
return value.isPresent();
}

public boolean isUnknown() {
return failure.isPresent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dev.hylfrd.farmhelper.navigation.simulation;

/** Typed reasons why a bounded prediction cannot return a trustworthy state. */
public enum FlyStoppingFailure {
INVALID_STATE(false),
MISSING_EVIDENCE(true),
STALE_EVIDENCE(true),
UNLOADED_EVIDENCE(true),
INCOMPLETE_EVIDENCE(true);

private final boolean evidenceFailure;

FlyStoppingFailure(boolean evidenceFailure) {
this.evidenceFailure = evidenceFailure;
}

public boolean evidenceFailure() {
return evidenceFailure;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.hylfrd.farmhelper.navigation.simulation;

/** Immutable movement input copied into the predictor. Stopping states require {@link #ZERO}. */
public record FlyStoppingInput(double strafe, double forward) {
public static final FlyStoppingInput ZERO = new FlyStoppingInput(0.0D, 0.0D);
private static final double INPUT_DAMPING = 0.98F;

public FlyStoppingInput {
requireFinite(strafe, "strafe");
requireFinite(forward, "forward");
}

public FlyStoppingInput damped() {
if (isZero()) {
return ZERO;
}
return new FlyStoppingInput(strafe * INPUT_DAMPING, forward * INPUT_DAMPING);
}

public boolean isZero() {
return strafe == 0.0D && forward == 0.0D;
}

private static void requireFinite(double value, String name) {
if (!Double.isFinite(value)) {
throw new IllegalArgumentException(name + " must be finite");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package dev.hylfrd.farmhelper.navigation.simulation;

import dev.hylfrd.farmhelper.runtime.snapshot.PositionSnapshot;

import java.util.Objects;
import java.util.Optional;

/** Immutable result that never exposes a partial state when required evidence is unknown. */
public record FlyStoppingPrediction(
FlyStoppingPredictionOutcome outcome,
Optional<FlyStoppingState> state,
int ticks,
Optional<FlyStoppingFailure> failure
) {
public FlyStoppingPrediction {
Objects.requireNonNull(outcome, "outcome");
Objects.requireNonNull(state, "state");
Objects.requireNonNull(failure, "failure");
if (ticks < 0 || ticks > FlyStoppingSimulator.MAX_TICKS) {
throw new IllegalArgumentException("ticks are outside the simulation budget");
}
boolean ticksMatch = switch (outcome) {
case STOPPED -> ticks >= 1;
case TICK_LIMIT -> ticks == FlyStoppingSimulator.MAX_TICKS;
case UNKNOWN -> ticks < FlyStoppingSimulator.MAX_TICKS;
};
if (!ticksMatch) {
throw new IllegalArgumentException("outcome and tick count do not agree");
}
boolean payloadMatches = switch (outcome) {
case STOPPED, TICK_LIMIT -> state.isPresent() && failure.isEmpty();
case UNKNOWN -> state.isEmpty() && failure.isPresent();
};
if (!payloadMatches) {
throw new IllegalArgumentException("outcome and payload do not agree");
}
}

static FlyStoppingPrediction stopped(FlyStoppingState state, int ticks) {
return known(FlyStoppingPredictionOutcome.STOPPED, state, ticks);
}

static FlyStoppingPrediction tickLimit(FlyStoppingState state) {
return known(FlyStoppingPredictionOutcome.TICK_LIMIT, state,
FlyStoppingSimulator.MAX_TICKS);
}

static FlyStoppingPrediction unknown(FlyStoppingFailure failure, int completedTicks) {
return new FlyStoppingPrediction(FlyStoppingPredictionOutcome.UNKNOWN, Optional.empty(),
completedTicks, Optional.of(Objects.requireNonNull(failure, "failure")));
}

private static FlyStoppingPrediction known(
FlyStoppingPredictionOutcome outcome,
FlyStoppingState state,
int ticks
) {
return new FlyStoppingPrediction(outcome,
Optional.of(Objects.requireNonNull(state, "state")), ticks, Optional.empty());
}

public boolean isKnown() {
return outcome != FlyStoppingPredictionOutcome.UNKNOWN;
}

public boolean isStopped() {
return outcome == FlyStoppingPredictionOutcome.STOPPED;
}

public boolean isUnknown() {
return outcome == FlyStoppingPredictionOutcome.UNKNOWN;
}

public Optional<PositionSnapshot> position() {
return state.map(FlyStoppingState::position);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.hylfrd.farmhelper.navigation.simulation;

/** Outcome of one bounded zero-input stopping prediction. */
public enum FlyStoppingPredictionOutcome {
STOPPED,
TICK_LIMIT,
UNKNOWN
}
Loading
Loading