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); }
}