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