XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

MyDimension

【Java】我的维度模组

公开
关注 0 Fork 0 Star 0
UTF-8
package com.xfestudio.mydimension.client.builder;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/** Client-owned, immutable-at-the-boundary state for world previews. */
public final class BuilderPreviewState {
    public enum Kind {
        BUILD(0.18F, 0.95F, 0.35F, 0.95F),
        DEMOLISH(0.96F, 0.20F, 0.20F, 0.95F),
        MISSING(1.00F, 0.82F, 0.12F, 1.00F),
        INVALID(1.00F, 0.48F, 0.10F, 0.95F),
        BLUEPRINT(0.20F, 0.55F, 1.00F, 0.82F),
        /** Client-only color used for loaded resonant supply anchors. */
        ANCHOR(0.72F, 0.24F, 0.96F, 1.00F);

        private final float red;
        private final float green;
        private final float blue;
        private final float alpha;

        Kind(float red, float green, float blue, float alpha) {
            this.red = red;
            this.green = green;
            this.blue = blue;
            this.alpha = alpha;
        }

        public float red() { return red; }
        public float green() { return green; }
        public float blue() { return blue; }
        public float alpha() { return alpha; }
    }

    public record Cell(BlockPos pos, BlockState state, Kind kind, boolean ghost) {
        public Cell {
            pos = pos.immutable();
        }

        public AABB bounds() {
            return new AABB(pos).inflate(0.002D);
        }
    }

    public record Selection(ResourceKey<Level> dimension, @Nullable BlockPos first, @Nullable BlockPos second) {
        public Selection {
            first = first == null ? null : first.immutable();
            second = second == null ? null : second.immutable();
        }

        public boolean active() {
            return first != null;
        }

        /** Both validated corners exist, so the cuboid can be saved again. */
        public boolean complete() {
            return first != null && second != null;
        }

        @Nullable
        public AABB bounds() {
            if (first == null || second == null) {
                return null;
            }
            BlockPos min = new BlockPos(
                    Math.min(first.getX(), second.getX()),
                    Math.min(first.getY(), second.getY()),
                    Math.min(first.getZ(), second.getZ())
            );
            BlockPos max = new BlockPos(
                    Math.max(first.getX(), second.getX()) + 1,
                    Math.max(first.getY(), second.getY()) + 1,
                    Math.max(first.getZ(), second.getZ()) + 1
            );
            return new AABB(min, max);
        }
    }

    public record Snapshot(ResourceKey<Level> dimension, List<Cell> cells, Selection selection,
                           @Nullable UUID activeJobId, boolean blueprintPreview,
                           boolean cancelable, int revision) {
        public Snapshot {
            cells = List.copyOf(cells);
        }
    }

    public record Candidate(ResourceKey<Level> dimension, BlockPos pos) {
        public Candidate {
            pos = pos.immutable();
        }
    }

    /** Exactly one crosshair focus is selected from all visible preview objects. */
    public enum FocusKind {
        DEPLOYMENT,
        SELECTION,
        MISSING,
        CANDIDATE
    }

    public record Focus(FocusKind kind, AABB bounds, double distance,
                        @Nullable Cell cell, @Nullable MissingGroup missingGroup) {
        public Focus(FocusKind kind, AABB bounds, double distance, @Nullable Cell cell) {
            this(kind, bounds, distance, cell, null);
        }
    }

    /** One 26-neighbour connected component of missing construction cells. */
    public record MissingGroup(List<Cell> cells, AABB bounds) {
        public MissingGroup {
            cells = List.copyOf(cells);
        }
    }

    record MissingTarget(MissingGroup group, Cell cell) { }

    /** Exact action represented by the currently focused cancelable object. */
    public enum CancelTarget {
        DEPLOYMENT,
        SELECTION,
        MISSING
    }

    private static final BuilderPreviewState INSTANCE = new BuilderPreviewState();

    @Nullable
    private volatile PublishedPreview published;
    @Nullable
    private volatile Focus focus;
    @Nullable
    private volatile Candidate controlCandidate;
    @Nullable
    private ResourceKey<Level> layerDimension;
    @Nullable
    private Selection layerSelection;
    @Nullable
    private UUID layerActiveJobId;
    private List<Cell> surfaceCells = List.of();
    private List<Cell> missingCells = List.of();
    private List<Cell> deploymentCells = List.of();
    private boolean deploymentActive;
    private boolean serverCancelable;
    private boolean layersInitialized;
    private int layerRevision;
    private record PublishedPreview(Snapshot snapshot,
                                    Map<BlockPos, MissingTarget> interactiveGroups,
                                    @Nullable AABB blueprintBounds) {
        private PublishedPreview {
            interactiveGroups = Map.copyOf(interactiveGroups);
        }
    }

    BuilderPreviewState() {
    }

    public static BuilderPreviewState get() {
        return INSTANCE;
    }

    @Nullable
    public Snapshot snapshot() {
        PublishedPreview frame = published;
        return frame == null ? null : frame.snapshot();
    }

    /**
     * Replaces every preview layer from one already-composed snapshot. Production callers should
     * prefer the layer-specific update methods below so unrelated workflows cannot erase each
     * other; this entry point remains useful at the immutable boundary and in focused tests.
     */
    public void accept(Snapshot value) {
        resetLayers(value.dimension());
        layerSelection = value.selection();
        layerActiveJobId = value.activeJobId();
        missingCells = value.cells().stream()
                .filter(cell -> cell.kind() == Kind.MISSING).toList();
        List<Cell> ordinary = value.cells().stream()
                .filter(cell -> cell.kind() != Kind.MISSING).toList();
        deploymentActive = value.blueprintPreview();
        deploymentCells = deploymentActive ? ordinary : List.of();
        surfaceCells = deploymentActive ? List.of() : ordinary;
        serverCancelable = value.cancelable() && value.activeJobId() != null;
        layerRevision = value.revision();
        publishComposite();
    }

    /** Replaces only the locally planned build/demolish surface. */
    public void updateSurfacePreview(ResourceKey<Level> dimension, List<Cell> cells) {
        ensureDimension(dimension);
        surfaceCells = List.copyOf(cells);
        advanceRevision(0);
        publishComposite();
    }

    /** Removes only the locally planned surface, retaining selection and material-waiting work. */
    public void clearSurfacePreview() {
        if (!layersInitialized || surfaceCells.isEmpty()) return;
        surfaceCells = List.of();
        advanceRevision(0);
        publishComposite();
    }

    /** Replaces only the copied source cuboid. */
    public void updateSelection(Selection selection) {
        ensureDimension(selection.dimension());
        layerSelection = selection;
        advanceRevision(0);
        publishComposite();
    }

    /**
     * Replaces only the server-owned material-waiting layer. A transient empty packet may be
     * ignored while the lightweight server snapshot still advertises the same active job (the
     * blueprint worker temporarily moves that job out of persistent pending storage while it runs).
     */
    public void updateMissingPreview(ResourceKey<Level> dimension, List<Cell> cells,
                                     @Nullable UUID activeJobId, boolean cancelable,
                                     int revision, boolean preserveTransientEmpty) {
        ensureDimension(dimension);
        if (preserveTransientEmpty && cells.isEmpty() && activeJobId == null
                && layerActiveJobId != null) {
            return;
        }
        missingCells = cells.stream()
                .filter(cell -> cell.kind() == Kind.MISSING).toList();
        layerActiveJobId = activeJobId;
        serverCancelable = cancelable && activeJobId != null;
        advanceRevision(revision);
        publishComposite();
    }

    /** Activates or refreshes the movable deployment without destroying background workflows. */
    public void updateDeploymentPreview(ResourceKey<Level> dimension, List<Cell> cells) {
        ensureDimension(dimension);
        deploymentActive = true;
        deploymentCells = List.copyOf(cells);
        // A surface target is meaningful only at the location from which it was planned. Once a
        // deployment takes control of targeting, discard that stale layer; it is replanned on the
        // first tick after the deployment is cancelled.
        surfaceCells = List.of();
        advanceRevision(0);
        publishComposite();
    }

    private void publishComposite() {
        if (!layersInitialized) {
            published = null;
            focus = null;
            return;
        }
        Selection selection = layerSelection == null
                || !Objects.equals(layerSelection.dimension(), layerDimension)
                ? new Selection(layerDimension, null, null) : layerSelection;
        List<Cell> foreground = deploymentActive ? deploymentCells : surfaceCells;
        Map<BlockPos, Cell> visible = new LinkedHashMap<>(
                Math.max(16, foreground.size() + missingCells.size()));
        for (Cell cell : foreground) visible.put(cell.pos(), cell);
        if (!deploymentActive) {
            // Missing material is actionable and therefore wins a same-position collision with a
            // passive surface projection. While a deployment is active it exclusively owns the
            // world overlay and focus; missing work remains retained in this layer and returns as
            // soon as placement is cancelled.
            for (Cell cell : missingCells) visible.put(cell.pos(), cell);
        }
        boolean cancelable = deploymentActive || selection.active()
                || serverCancelable && layerActiveJobId != null;
        Snapshot snapshot = new Snapshot(layerDimension, List.copyOf(visible.values()), selection,
                layerActiveJobId, deploymentActive, cancelable, layerRevision);
        publishSnapshot(snapshot, deploymentActive ? deploymentCells : List.of());
    }

    private void publishSnapshot(Snapshot value, List<Cell> deploymentBoundsCells) {
        focus = null;
        if (value.cells().isEmpty()) {
            published = new PublishedPreview(value, Map.of(), null);
            return;
        }
        Map<BlockPos, Cell> targets = new LinkedHashMap<>();
        int minX = Integer.MAX_VALUE;
        int minY = Integer.MAX_VALUE;
        int minZ = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE;
        int maxY = Integer.MIN_VALUE;
        int maxZ = Integer.MIN_VALUE;
        for (Cell cell : value.cells()) {
            if (cell.kind() == Kind.MISSING) {
                targets.put(cell.pos(), cell);
            }
        }
        for (Cell cell : deploymentBoundsCells) {
            minX = Math.min(minX, cell.pos().getX());
            minY = Math.min(minY, cell.pos().getY());
            minZ = Math.min(minZ, cell.pos().getZ());
            maxX = Math.max(maxX, cell.pos().getX());
            maxY = Math.max(maxY, cell.pos().getY());
            maxZ = Math.max(maxZ, cell.pos().getZ());
        }
        Map<BlockPos, MissingTarget> groups = targets.isEmpty()
                ? Map.of() : indexMissingGroups(targets);
        AABB bounds = deploymentActive && minX != Integer.MAX_VALUE
                ? new AABB(minX, minY, minZ, maxX + 1.0D, maxY + 1.0D, maxZ + 1.0D)
                : null;
        // Publish the immutable snapshot and all derived indexes together. Readers can never
        // observe cells from one generation with bounds/groups from another generation.
        published = new PublishedPreview(value, groups, bounds);
    }

    public void clear() {
        focus = null;
        controlCandidate = null;
        published = null;
        clearLayers();
    }

    public void clearLocalWorkflow() {
        if (!layersInitialized) return;
        surfaceCells = List.of();
        missingCells = List.of();
        deploymentCells = List.of();
        deploymentActive = false;
        layerActiveJobId = null;
        serverCancelable = false;
        layerSelection = new Selection(layerDimension, null, null);
        advanceRevision(0);
        publishComposite();
        focus = null;
    }

    /** Removes only the transformed placement, retaining the copied source cuboid. */
    public void clearPlacementPreview() {
        if (!layersInitialized || !deploymentActive) return;
        deploymentActive = false;
        deploymentCells = List.of();
        advanceRevision(0);
        publishComposite();
    }

    /** Removes only the copied source selection, retaining deployment or missing previews. */
    public void clearSelection() {
        if (!layersInitialized || layerSelection == null || !layerSelection.active()) return;
        layerSelection = new Selection(layerDimension, null, null);
        advanceRevision(0);
        publishComposite();
    }

    /** Removes only the yellow task cells after a transaction-specific cancellation. */
    public void clearMissingPreview() {
        if (!layersInitialized || layerActiveJobId == null) return;
        missingCells = List.of();
        layerActiveJobId = null;
        serverCancelable = false;
        advanceRevision(0);
        publishComposite();
    }

    private void ensureDimension(ResourceKey<Level> dimension) {
        if (!layersInitialized || !Objects.equals(layerDimension, dimension)) {
            resetLayers(dimension);
        }
    }

    private void resetLayers(ResourceKey<Level> dimension) {
        clearLayers();
        layerDimension = dimension;
        layerSelection = new Selection(dimension, null, null);
        layersInitialized = true;
    }

    private void clearLayers() {
        layerDimension = null;
        layerSelection = null;
        layerActiveJobId = null;
        surfaceCells = List.of();
        missingCells = List.of();
        deploymentCells = List.of();
        deploymentActive = false;
        serverCancelable = false;
        layersInitialized = false;
        layerRevision = 0;
    }

    private void advanceRevision(int suggestedRevision) {
        int next = layerRevision == Integer.MAX_VALUE ? 1 : layerRevision + 1;
        layerRevision = Math.max(next, suggestedRevision);
    }

    public boolean isBlueprintPreviewActive() {
        Snapshot value = snapshot();
        return value != null && value.blueprintPreview();
    }

    /** Checks the deployment layer itself; composite cells can additionally contain missing work. */
    public boolean hasCompleteDeploymentGeneration(int expectedCells) {
        return deploymentActive && deploymentCells.size() == Math.max(0, expectedCells);
    }

    public boolean hasSaveableSelection() {
        Snapshot value = snapshot();
        return value != null && value.selection() != null && value.selection().complete();
    }

    /** Transform actions need a deployment; SAVE only needs the persistent source cuboid. */
    public boolean hasBlueprintWheelActions() {
        return isBlueprintPreviewActive() || hasSaveableSelection();
    }

    public boolean hasCancelableWorkflow() {
        Snapshot value = snapshot();
        return value != null && value.cancelable();
    }

    public boolean hasFocusedCancelableTarget() {
        return focusedCancelTarget() != null;
    }

    /** Resolves focus and workflow state once so an input event cannot cancel a different object. */
    @Nullable
    public CancelTarget focusedCancelTarget() {
        return cancelTarget(snapshot(), focus);
    }

    @Nullable
    public UUID activeJobId() {
        Snapshot value = snapshot();
        return value == null ? null : value.activeJobId();
    }

    @Nullable
    public Cell virtualTarget() {
        Focus current = focus;
        return current != null && current.kind() == FocusKind.MISSING ? current.cell() : null;
    }

    @Nullable
    public Focus focus() {
        return focus;
    }

    @Nullable
    public Candidate controlCandidate() {
        return controlCandidate;
    }

    @Nullable
    public AABB blueprintBounds() {
        PublishedPreview frame = published;
        return frame == null ? null : frame.blueprintBounds();
    }

    public void setControlCandidate(ResourceKey<Level> dimension, BlockPos position) {
        Candidate current = controlCandidate;
        if (current == null || !current.dimension().equals(dimension)
                || !current.pos().equals(position)) {
            controlCandidate = new Candidate(dimension, position);
        }
    }

    public void clearControlCandidate() {
        controlCandidate = null;
    }

    public void clearHover() {
        focus = null;
    }

    /**
     * Server task previews currently carry no client-owned capture corners. Preserve an active
     * same-dimension selection unless the packet explicitly supplies replacement corners.
     */
    static Selection mergeSelection(ResourceKey<Level> dimension, @Nullable BlockPos first,
                                    @Nullable BlockPos second, @Nullable Snapshot current) {
        if (first != null || second != null) {
            return new Selection(dimension, first, second);
        }
        Selection currentSelection = current == null ? null : current.selection();
        boolean sameDimension = current != null && currentSelection != null
                && current.dimension().equals(dimension)
                && currentSelection.dimension().equals(dimension);
        if (shouldPreserveSelection(first, second, sameDimension, currentSelection)) {
            return current.selection();
        }
        return new Selection(dimension, null, null);
    }

    static boolean shouldPreserveSelection(@Nullable BlockPos first, @Nullable BlockPos second,
                                           boolean sameDimension, @Nullable Selection current) {
        return first == null && second == null && sameDimension
                && current != null && current.active();
    }

    static boolean mergeCancelable(boolean serverCancelable, boolean blueprintPreview,
                                   Selection selection) {
        return serverCancelable || blueprintPreview || selection.active();
    }

    /**
     * A normal surface/selection/deployment preview is client-owned. Server synchronization also
     * sends an empty packet to mean "there is no material-waiting task"; accepting that packet
     * verbatim creates a one-frame blank before the local planner republishes its result. Preserve
     * client-owned content in that case, while an actual task completion still removes its yellow
     * cells immediately.
     */
    static Snapshot mergeServerSnapshot(@Nullable Snapshot current, Snapshot incoming,
                                        boolean emptyServerWorkflow) {
        if (!emptyServerWorkflow || current == null
                || !Objects.equals(current.dimension(), incoming.dimension())) return incoming;

        if (current.activeJobId() != null) {
            List<Cell> remaining = current.cells().stream()
                    .filter(cell -> cell.kind() != Kind.MISSING).toList();
            boolean cancelable = current.blueprintPreview() || incoming.selection().active();
            return new Snapshot(incoming.dimension(), remaining, incoming.selection(), null,
                    current.blueprintPreview(), cancelable, incoming.revision());
        }

        boolean clientOwned = !current.cells().isEmpty() || current.blueprintPreview()
                || current.selection().active();
        return clientOwned ? current : incoming;
    }

    /**
     * Updates the yellow/blue virtual-cell under the crosshair. A voxel DDA
     * visits at most a few hundred positions at 64-block reach and performs
     * O(1) lookups, rather than scanning the whole blueprint every tick.
     */
    public void updateHoveredTarget(Minecraft minecraft, float partialTick, double maximumDistance) {
        updateHoveredTarget(minecraft, partialTick, maximumDistance,
                BuilderPreviewVisibility.NORMAL);
    }

    /** Applies the active Ctrl context to hit testing as well as drawing hidden layers. */
    void updateHoveredTarget(Minecraft minecraft, float partialTick, double maximumDistance,
                             BuilderPreviewVisibility visibility) {
        PublishedPreview frame = published;
        Snapshot value = frame == null ? null : frame.snapshot();
        Candidate candidate = controlCandidate;
        if (minecraft.player == null || minecraft.level == null) {
            focus = null;
            return;
        }
        boolean snapshotVisible = value != null
                && minecraft.level.dimension().equals(value.dimension());
        boolean candidateVisible = candidate != null
                && minecraft.level.dimension().equals(candidate.dimension());
        if (!snapshotVisible && !candidateVisible) {
            focus = null;
            return;
        }
        Map<BlockPos, MissingTarget> groups = snapshotVisible
                && visibility.showsFocus(FocusKind.MISSING)
                ? frame.interactiveGroups() : Map.of();
        AABB visibleDeploymentBounds = snapshotVisible && value.blueprintPreview()
                && visibility.showsDeploymentFrame()
                ? frame.blueprintBounds() : null;
        AABB visibleSelectionBounds = snapshotVisible
                && visibility.showsSelection(value.blueprintPreview())
                && value.selection() != null
                ? value.selection().bounds() : null;
        if (groups.isEmpty() && visibleDeploymentBounds == null
                && visibleSelectionBounds == null && !candidateVisible) {
            focus = null;
            return;
        }

        Vec3 origin = minecraft.player.getEyePosition(partialTick);
        Vec3 direction = minecraft.player.getViewVector(partialTick).normalize();
        double visibleDistance = maximumDistance;
        // Vanilla's cached hit result is limited by the ordinary interaction
        // reach. Re-pick at the server-advertised builder reach so a virtual
        // cell cannot be selected through a real obstruction farther away.
        HitResult hitResult = minecraft.player.pick(maximumDistance, partialTick, false);
        if (hitResult instanceof BlockHitResult blockHit && hitResult.getType() == HitResult.Type.BLOCK) {
            visibleDistance = Math.min(visibleDistance,
                    origin.distanceTo(blockHit.getLocation()) + 0.025D);
        }

        // Deployment is the one intentional type priority. Once the ray enters its full
        // volume, it wins over source-selection and missing-material objects on that ray.
        RayInterval deploymentHit = rayInterval(visibleDeploymentBounds, origin, direction, visibleDistance);
        if (deploymentHit != null) {
            focus = new Focus(FocusKind.DEPLOYMENT, visibleDeploymentBounds,
                    deploymentHit.enter(), null);
            return;
        }

        Focus nearest = null;
        RayInterval selectionHit = rayInterval(visibleSelectionBounds, origin, direction, visibleDistance);
        if (selectionHit != null) {
            nearest = new Focus(FocusKind.SELECTION, visibleSelectionBounds,
                    selectionHit.enter(), null);
        }

        nearest = nearer(nearest, focusMissing(origin, direction, visibleDistance, groups));

        if (candidateVisible) {
            AABB bounds = new AABB(candidate.pos()).inflate(0.006D);
            RayInterval candidateHit = rayInterval(bounds, origin, direction, visibleDistance);
            if (candidateHit != null) {
                nearest = nearer(nearest, new Focus(FocusKind.CANDIDATE, bounds, candidateHit.enter(),
                        new Cell(candidate.pos(), Blocks.AIR.defaultBlockState(),
                                Kind.BLUEPRINT, false)));
            }
        }
        focus = nearest;
    }

    @Nullable
    private static MissingRayHit raycastInteractive(Vec3 origin, Vec3 direction, double maximumDistance,
                                                    Map<BlockPos, MissingTarget> groups) {
        if (groups.isEmpty() || maximumDistance <= 0.0D) return null;
        int x = floor(origin.x);
        int y = floor(origin.y);
        int z = floor(origin.z);
        int stepX = direction.x > 0.0D ? 1 : direction.x < 0.0D ? -1 : 0;
        int stepY = direction.y > 0.0D ? 1 : direction.y < 0.0D ? -1 : 0;
        int stepZ = direction.z > 0.0D ? 1 : direction.z < 0.0D ? -1 : 0;
        double deltaX = stepX == 0 ? Double.POSITIVE_INFINITY : Math.abs(1.0D / direction.x);
        double deltaY = stepY == 0 ? Double.POSITIVE_INFINITY : Math.abs(1.0D / direction.y);
        double deltaZ = stepZ == 0 ? Double.POSITIVE_INFINITY : Math.abs(1.0D / direction.z);
        double nextX = boundaryDistance(origin.x, x, stepX, direction.x);
        double nextY = boundaryDistance(origin.y, y, stepY, direction.y);
        double nextZ = boundaryDistance(origin.z, z, stepZ, direction.z);
        BlockPos.MutableBlockPos cursor = new BlockPos.MutableBlockPos();
        double travelled = 0.0D;
        while (travelled <= maximumDistance + 1.0E-5D) {
            cursor.set(x, y, z);
            MissingTarget target = groups.get(cursor);
            if (target != null) return new MissingRayHit(target.group(), target.cell());
            if (nextX <= nextY && nextX <= nextZ) {
                x += stepX;
                travelled = nextX;
                nextX += deltaX;
            } else if (nextY <= nextZ) {
                y += stepY;
                travelled = nextY;
                nextY += deltaY;
            } else {
                z += stepZ;
                travelled = nextZ;
                nextZ += deltaZ;
            }
        }
        return null;
    }

    @Nullable
    static Focus focusMissing(Vec3 origin, Vec3 direction, double maximumDistance,
                              Map<BlockPos, MissingTarget> groups) {
        MissingRayHit hit = raycastInteractive(origin, direction, maximumDistance, groups);
        if (hit == null) return null;
        // Interaction uses the exact cell volume. Cell#bounds is slightly inflated only to avoid
        // visual z-fighting and must not make a farther group win by a few millimetres.
        RayInterval interval = rayInterval(new AABB(hit.cell().pos()), origin, direction, maximumDistance);
        if (interval == null) return null;
        return new Focus(FocusKind.MISSING, hit.group().bounds(), interval.enter(),
                hit.cell(), hit.group());
    }

    /** Builds stable connected components using face, edge, and corner adjacency. */
    static Map<BlockPos, MissingTarget> indexMissingGroups(Map<BlockPos, Cell> cells) {
        if (cells.isEmpty()) return Map.of();
        Long2ObjectOpenHashMap<Cell> cellsByPosition = new Long2ObjectOpenHashMap<>(cells.size());
        cells.forEach((position, cell) -> cellsByPosition.put(position.asLong(), cell));
        LongOpenHashSet visited = new LongOpenHashSet(cells.size());
        Map<BlockPos, MissingTarget> result = new HashMap<>(Math.max(16, cells.size() * 2));
        LongArrayFIFOQueue pending = new LongArrayFIFOQueue();
        for (Map.Entry<BlockPos, Cell> seed : cells.entrySet()) {
            long seedPosition = seed.getKey().asLong();
            if (!visited.add(seedPosition)) continue;
            List<Cell> members = new ArrayList<>();
            pending.enqueue(seedPosition);
            int minX = Integer.MAX_VALUE;
            int minY = Integer.MAX_VALUE;
            int minZ = Integer.MAX_VALUE;
            int maxX = Integer.MIN_VALUE;
            int maxY = Integer.MIN_VALUE;
            int maxZ = Integer.MIN_VALUE;
            while (!pending.isEmpty()) {
                long packedPosition = pending.dequeueLong();
                Cell cell = cellsByPosition.get(packedPosition);
                if (cell == null) continue;
                int x = BlockPos.getX(packedPosition);
                int y = BlockPos.getY(packedPosition);
                int z = BlockPos.getZ(packedPosition);
                members.add(cell);
                minX = Math.min(minX, x);
                minY = Math.min(minY, y);
                minZ = Math.min(minZ, z);
                maxX = Math.max(maxX, x);
                maxY = Math.max(maxY, y);
                maxZ = Math.max(maxZ, z);
                for (int dx = -1; dx <= 1; dx++) {
                    for (int dy = -1; dy <= 1; dy++) {
                        for (int dz = -1; dz <= 1; dz++) {
                            if (dx == 0 && dy == 0 && dz == 0) continue;
                            long neighbour = BlockPos.asLong(x + dx, y + dy, z + dz);
                            if (cellsByPosition.containsKey(neighbour) && visited.add(neighbour)) {
                                pending.enqueue(neighbour);
                            }
                        }
                    }
                }
            }
            MissingGroup group = new MissingGroup(members,
                    new AABB(minX, minY, minZ, maxX + 1.0D, maxY + 1.0D, maxZ + 1.0D));
            for (Cell member : members) result.put(member.pos(), new MissingTarget(group, member));
        }
        return Collections.unmodifiableMap(result);
    }

    @Nullable
    static CancelTarget cancelTarget(@Nullable Snapshot value, @Nullable Focus current) {
        if (value == null || current == null) return null;
        return switch (current.kind()) {
            case DEPLOYMENT -> value.blueprintPreview() ? CancelTarget.DEPLOYMENT : null;
            case SELECTION -> value.selection().active() ? CancelTarget.SELECTION : null;
            case MISSING -> value.cancelable() && value.activeJobId() != null
                    && current.missingGroup() != null
                    ? CancelTarget.MISSING : null;
            case CANDIDATE -> null;
        };
    }

    private static int floor(double value) {
        int integer = (int) value;
        return value < integer ? integer - 1 : integer;
    }

    private static double boundaryDistance(double origin, int cell, int step, double direction) {
        if (step == 0) return Double.POSITIVE_INFINITY;
        double boundary = step > 0 ? cell + 1.0D : cell;
        return (boundary - origin) / direction;
    }

    @Nullable
    private static Focus nearer(@Nullable Focus current, @Nullable Focus candidate) {
        if (candidate == null) return current;
        if (current == null || candidate.distance() + 1.0E-6D < current.distance()) {
            return candidate;
        }
        return current;
    }

    /** Slab intersection reports entry into the whole cuboid, not merely its twelve edges. */
    @Nullable
    private static RayInterval rayInterval(@Nullable AABB box, Vec3 origin,
                                           Vec3 direction, double maximumDistance) {
        if (box == null || maximumDistance < 0.0D) return null;
        double enter = 0.0D;
        double exit = maximumDistance;
        double[] origins = { origin.x, origin.y, origin.z };
        double[] directions = { direction.x, direction.y, direction.z };
        double[] minimums = { box.minX, box.minY, box.minZ };
        double[] maximums = { box.maxX, box.maxY, box.maxZ };
        for (int axis = 0; axis < 3; axis++) {
            double component = directions[axis];
            if (Math.abs(component) < 1.0E-9D) {
                if (origins[axis] < minimums[axis] || origins[axis] > maximums[axis]) return null;
                continue;
            }
            double first = (minimums[axis] - origins[axis]) / component;
            double second = (maximums[axis] - origins[axis]) / component;
            if (first > second) {
                double swap = first;
                first = second;
                second = swap;
            }
            enter = Math.max(enter, first);
            exit = Math.min(exit, second);
            if (exit + 1.0E-7D < enter) return null;
        }
        return exit < 0.0D || enter > maximumDistance ? null
                : new RayInterval(Math.max(0.0D, enter), Math.min(maximumDistance, exit));
    }

    private record RayInterval(double enter, double exit) { }

    private record MissingRayHit(MissingGroup group, Cell cell) { }
}