Volver al índice

src/main/java/ar/com/companeros/runtime/ToolRuntimeExtensionTests.java

package ar.com.companeros.runtime;

import ar.com.companeros.Companeros;
import ar.com.companeros.body.PlayerSessions;
import ar.com.companeros.identity.*;
import com.google.gson.*;
import java.util.UUID;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.core.BlockPos;
import net.minecraft.core.GlobalPos;
import net.minecraft.gametest.framework.GameTest;
import net.minecraft.gametest.framework.GameTestHelper;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.*;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.gametest.GameTestHolder;
import net.minecraftforge.gametest.PrefixGameTestTemplate;

/** Recorrido completo por el controlador: propuesta, ensayos físicos y uso compartido. */
@GameTestHolder(Companeros.ID)
@PrefixGameTestTemplate(false)
public final class ToolRuntimeExtensionTests {
    @GameTest(batch = "runtime_07_sneak", template = "empty", timeoutTicks = 30)
    public void researcherCanCrouchAndStandThroughTheRuntime(GameTestHelper helper) {
        var server=helper.getLevel().getServer(); UUID author=UUID.randomUUID(), peer=UUID.randomUUID();
        safely(server, author, peer, () -> {
            fixture(helper, author); AgentRoles.get(server).assign(author, AgentRoles.Role.INVESTIGADOR);
            helper.assertTrue(AgentRuntime.runTool(console(server), name(author), "sneak", "{\"enabled\":true}") == 1, "Un investigador puede usar movimiento permitido");
            helper.runAfterDelay(3, () -> safely(server, author, peer, () -> {
                helper.assertTrue(PlayerSessions.find(server, author).isShiftKeyDown(), "El cuerpo del jugador se agacha realmente");
                helper.assertTrue(AgentRuntime.runTool(console(server), name(author), "sneak", "{\"enabled\":false}") == 1, "Puede terminar la postura voluntariamente");
                helper.runAfterDelay(3, () -> safely(server, author, peer, () -> {
                    helper.assertTrue(!PlayerSessions.find(server, author).isShiftKeyDown(), "La postura no queda bloqueada");
                    cleanup(server, author); cleanup(server, peer); helper.succeed();
                }));
            }));
        });
    }
    @GameTest(batch = "runtime_06_extensions", template = "empty", timeoutTicks = 120)
    public void runtimeCreatesTrialsCertifiesAndSharesWithoutBypassingRole(GameTestHelper helper) {
        MinecraftServer server = helper.getLevel().getServer();
        UUID author = UUID.randomUUID(), peer = UUID.randomUUID();
        String tool = "rt_planks_" + author.toString().substring(0, 8);
        safely(server, author, peer, () -> {
            ServerPlayer body = fixture(helper, author);
            body.getInventory().add(new ItemStack(Items.OAK_LOG, 3));
            JsonObject envelope = new JsonObject(); envelope.addProperty("manifest", manifest(tool).toString());
            helper.assertTrue(AgentRuntime.runTool(console(server), name(author), "propose_tool", envelope.toString()) == 1,
                "El controlador inicia la propuesta mediante una capacidad permitida del propio jugador");
            helper.runAfterDelay(3, () -> safely(server, author, peer, () -> {
                helper.assertTrue(count(PlayerSessions.find(server, author), Items.OAK_LOG) == 3
                        && count(PlayerSessions.find(server, author), Items.OAK_PLANKS) == 0
                        && AgentRuntime.toolNames(server).stream().noneMatch(row -> row.startsWith(tool + " v")),
                    "Proponer no fabrica ni certifica antes de las pruebas físicas");
                trial(helper, server, author, peer, tool, 1);
            }));
        });
    }

    // Cada ensayo espera el resultado del runtime; no llama directamente a primitivas ni crea éxitos artificiales.
    private static void trial(GameTestHelper helper, MinecraftServer server, UUID author, UUID peer, String tool, int number) {
        helper.assertTrue(AgentRuntime.runTool(console(server), name(author), tool, "{}") == 1,
            "El autor puede iniciar un ensayo de su propuesta todavía pendiente");
        helper.runAfterDelay(3, () -> safely(server, author, peer, () -> {
            ServerPlayer body = PlayerSessions.find(server, author);
            helper.assertTrue(body != null && count(body, Items.OAK_LOG) == 3 - number
                    && count(body, Items.OAK_PLANKS) == number * 4,
                "El ensayo consume exactamente un tronco y obtiene cuatro tablas del inventario real");
            boolean certified = AgentRuntime.toolNames(server).stream().anyMatch(row -> row.startsWith(tool + " v"));
            helper.assertTrue(certified == (number == 3), "La herramienta se comparte sólo después del tercer éxito útil");
            if (number < 3) { trial(helper, server, author, peer, tool, number + 1); return; }
            cleanup(server, author); // El banco de GameTests permite un único participante conectado.
            ServerPlayer other = fixture(helper, peer); other.getInventory().add(new ItemStack(Items.OAK_LOG));
            AgentRuntime.setRole(console(server), name(peer), AgentRoles.Role.INVESTIGADOR);
            helper.assertTrue(AgentRuntime.runTool(console(server), name(peer), tool, "{}") == 0
                    && count(other, Items.OAK_LOG) == 1 && count(other, Items.OAK_PLANKS) == 0,
                "Una herramienta certificada no permite al investigador evadir la restricción de producción");
            AgentRuntime.setRole(console(server), name(peer), AgentRoles.Role.GENERALISTA);
            helper.assertTrue(AgentRuntime.runTool(console(server), name(peer), tool, "{}") == 1,
                "Otro generalista puede ejecutar la composición aprendida por el primer bot");
            helper.runAfterDelay(3, () -> safely(server, author, peer, () -> {
                ServerPlayer completed = PlayerSessions.find(server, peer);
                helper.assertTrue(completed != null && count(completed, Items.OAK_LOG) == 0
                        && count(completed, Items.OAK_PLANKS) == 4
                        && AgentJournal.get(server).entry(peer).outcome().startsWith(tool + ":SUCCEEDED:"),
                    "El uso compartido conserva identidad, recursos propios y resultado observado del segundo jugador");
                cleanup(server, author); cleanup(server, peer); helper.succeed();
            }));
        }));
    }

    // Fixtures separadas del mundo real: los únicos recursos iniciales los proporciona la prueba.
    private static ServerPlayer fixture(GameTestHelper helper, UUID actor) {
        PlayerSessions.install(); AgentRuntime.install();
        for (int x = 0; x <= 13; x++) for (int z = 0; z <= 13; z++) helper.setBlock(new BlockPos(x, 1, z), Blocks.DIRT);
        BlockPos position = helper.absolutePos(new BlockPos(3, 2, 3)); MinecraftServer server = helper.getLevel().getServer();
        AgentRegistry.get(server).register(actor, name(actor), null, GlobalPos.of(helper.getLevel().dimension(), position), "quieto");
        AgentRegistry.get(server).markState(actor, AgentState.ACTIVE);
        return PlayerSessions.create(server, actor, name(actor), helper.getLevel(), position);
    }

    private static JsonObject manifest(String id) {
        JsonObject tool = new JsonObject(); tool.addProperty("schema", 1); tool.addProperty("id", id);
        tool.addProperty("version", 1); tool.addProperty("description", "Transformar un tronco propio en cuatro tablas, sin entradas externas");
        JsonArray permissions = new JsonArray(); permissions.add("CRAFT"); tool.add("permissions", permissions);
        tool.add("dependencies", new JsonArray()); JsonObject limits = new JsonObject();
        limits.addProperty("maxTicks", 100); limits.addProperty("maxSteps", 1); tool.add("limits", limits);
        JsonObject step = new JsonObject(); step.addProperty("primitive", "craft"); step.addProperty("timeoutTicks", 80);
        JsonObject arguments = new JsonObject(); arguments.addProperty("item", "minecraft:oak_planks"); arguments.addProperty("count", 4);
        step.add("args", arguments); JsonArray steps = new JsonArray(); steps.add(step); tool.add("steps", steps);
        return tool;
    }

    private static void safely(MinecraftServer server, UUID author, UUID peer, Runnable action) {
        try { action.run(); }
        catch (RuntimeException | Error failure) { cleanup(server, author); cleanup(server, peer); throw failure; }
    }
    private static void cleanup(MinecraftServer server, UUID actor) {
        AgentIdentity identity = AgentRegistry.get(server).find(actor).orElse(null);
        try {
            if (identity != null && identity.state() != AgentState.RETIRED && identity.state() != AgentState.LEGACY)
                AgentRuntime.retire(console(server), identity.name());
        } finally {
            PlayerSessions.remove(server, actor);
            if (identity != null) AgentRegistry.get(server).markState(actor, AgentState.RETIRED);
        }
    }
    private static int count(ServerPlayer player, Item item) {
        if (player == null) return 0;
        int count = 0;
        for (int slot = 0; slot < player.getInventory().getContainerSize(); slot++)
            if (player.getInventory().getItem(slot).is(item)) count += player.getInventory().getItem(slot).getCount();
        return count;
    }
    private static CommandSourceStack console(MinecraftServer server) {
        return server.createCommandSourceStack().withPermission(4).withSuppressedOutput();
    }
    private static String name(UUID actor) { return "Tr_" + actor.toString().substring(0, 8); }
}