package ar.com.companeros.runtime; import java.util.*; import net.minecraft.nbt.*; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.saveddata.SavedData; /** Checkpoints de objetivos. Un reinicio conserva contexto y exige volver a observar antes de actuar. */ public final class AgentJournal extends SavedData { public record Entry(String goal, String outcome, int consecutiveFailures, boolean interrupted, long updatedAtUtc) { } private final Map entries = new HashMap<>(); public static AgentJournal get(MinecraftServer server) { if (!server.isSameThread()) throw new IllegalStateException("El diario exige el hilo del servidor"); return server.overworld().getDataStorage().computeIfAbsent(AgentJournal::load, AgentJournal::new, "companeros_objetivos"); } public Entry entry(UUID id) { return entries.getOrDefault(id, new Entry("Sobrevivir y evaluar el entorno", "", 0, false, 0)); } public void checkpoint(UUID id, String goal, String outcome, int failures, boolean interrupted) { if (entries.size() >= 256 && !entries.containsKey(id)) throw new IllegalStateException("Diario lleno"); entries.put(id, new Entry(limit(goal, 512), limit(outcome, 512), Math.max(0, Math.min(100, failures)), interrupted, System.currentTimeMillis())); setDirty(); } @Override public CompoundTag save(CompoundTag root) { root.putInt("Schema", 1); ListTag list = new ListTag(); for (var item : entries.entrySet()) { CompoundTag tag = new CompoundTag(); Entry e = item.getValue(); tag.putUUID("Id", item.getKey()); tag.putString("Goal", e.goal()); tag.putString("Outcome", e.outcome()); tag.putInt("Failures", e.consecutiveFailures()); tag.putBoolean("Interrupted", e.interrupted()); tag.putLong("Updated", e.updatedAtUtc()); list.add(tag); } root.put("Entries", list); return root; } public static AgentJournal load(CompoundTag root) { if (root.getInt("Schema") > 1) throw new IllegalStateException("Diario de una versión posterior"); AgentJournal data = new AgentJournal(); ListTag list = root.getList("Entries", 10); if (list.size() > 256) throw new IllegalStateException("Diario fuera de límite"); for (int i = 0; i < list.size(); i++) { CompoundTag tag = list.getCompound(i); UUID id = tag.getUUID("Id"); if (data.entries.containsKey(id)) throw new IllegalStateException("Entrada duplicada"); data.entries.put(id, new Entry(limit(tag.getString("Goal"), 512), limit(tag.getString("Outcome"), 512), Math.max(0, Math.min(100, tag.getInt("Failures"))), tag.getBoolean("Interrupted"), Math.max(0, tag.getLong("Updated")))); } return data; } private static String limit(String value, int count) { return value.length() > count ? value.substring(0, count) : value; } }