package workbench; import java.util.List; import java.util.Random; /** * Agent that walks the manifold adjacency graph, injecting heat. * * Walk policy: 85% greedy (hottest neighbor), 15% random exploration. * Injects injectAmt heat at current vertex each step. * Life decays 0.05/step; respawns at random vertex when life <= 0. */ public class Friend { public static final String[] IDS = {"G","R","C","Y","V"}; public static final int[] COLORS = {0x68ff9a, 0xff3232, 0x00eeff, 0xffd700, 0x9400d3}; public final int idx; public final String id; public float life = 1000f; public float sap = 0f; public int blooms = 0; public int vIdx; // current vertex index on manifold // position in 3D (copied from manifold vertex each step) public float x, y, z; private final float injectAmt; private final float explorePct; private final Random rng; private final Manifold manifold; // callback for PCB log lines (set by Workbench) public Runnable onRespawn; public Runnable onBloom; public Friend(int idx, Manifold manifold) { this.idx = idx; this.id = IDS[idx]; this.manifold = manifold; this.injectAmt = 8.0f; this.explorePct = 0.15f; this.rng = new Random(); this.vIdx = rng.nextInt(Math.max(1, manifold.vertexCount)); syncPosition(); } public void compute() { List neighbors = manifold.adjacency.get(vIdx); if (!neighbors.isEmpty()) { int best = neighbors.get(0); for (int n : neighbors) { if (manifold.heat[n] > manifold.heat[best]) best = n; } vIdx = (rng.nextFloat() < explorePct) ? neighbors.get(rng.nextInt(neighbors.size())) : best; } syncPosition(); life -= 0.05f; sap += 0.02f; if (vIdx < manifold.heat.length) manifold.heat[vIdx] += injectAmt; if (life <= 0) { vIdx = rng.nextInt(Math.max(1, manifold.vertexCount)); life = 1000f; sap = 0f; if (onRespawn != null) onRespawn.run(); } if (sap > 20) { blooms++; sap = 0f; if (onBloom != null) onBloom.run(); } } private void syncPosition() { if (manifold.built && vIdx < manifold.vertexCount) { // x/y from originalPositions — avoids per-frame wobble noise jitter // z from positions — includes oracle heat displacement x = manifold.originalPositions[vIdx*3]; y = manifold.originalPositions[vIdx*3+1]; z = manifold.positions[vIdx*3+2]; } } /** Relocate to random vertex (call after manifold rebuild). */ public void relocate() { if (manifold.vertexCount > 0) vIdx = rng.nextInt(manifold.vertexCount); life = 1000f; sap = 0f; syncPosition(); } }