Skip to content

Memory and Roles

AzureDoom edited this page Aug 29, 2026 · 1 revision

Memory and Roles

Two smaller, optional packages: AgentMemory for state that should outlive a single tick but isn't quite core simulation state, and the role packages for soft, advisory group-role assignment.

AgentMemory, timestamped, cross-tick recall

Unlike Blackboard (per-tick AI state), AgentMemory entries carry a timestamp so callers can decide for themselves how stale a remembered value is allowed to be before it's worth refreshing.

public static final MemoryKey<BlockPos> NEAREST_WATER = MemoryKey.of("nearest_water", BlockPos.class);

var memory = new AgentMemory();

memory.remember(NEAREST_WATER, waterPos, currentTick);

var entry = memory.recall(NEAREST_WATER);           // MemoryEntry<BlockPos>, or null
var fresh = memory.recallIfFresh(NEAREST_WATER, currentTick, 200); // value only if <=200 ticks old, else null

memory.forget(NEAREST_WATER);
memory.has(NEAREST_WATER);

Scan-and-remember helper

AgentMemory also generalizes the "find nearest position matching a condition, remember it, skip re-scanning next time" pattern:

memory.findAndRemember(level, origin, radius, candidatePos -> isSuitable(candidatePos), NEAREST_WATER, currentTick);

This is a plain brute-force cube scan. For large radii or hot-path use, maintain your own spatial index (as Ovomorphosis's ResinWebRegistry does) and remember that result here rather than calling this helper repeatedly.

MemoryKey<T> mirrors BlackboardKey<T>, a stable string id paired with a Class<T> for the safe cast on read.

Soft roles: AgentRole, RoleAssignment, RoleSelector

A "role" is a soft, advisory intent label a planner assigns an agent for a while (the motivating example: Ovomorphosis's XenoRole, hunter, guard, scout). Roles are meant to bias GOAP goal scoring, not to gate what an agent is mechanically allowed to do.

Declaring a role enum

public enum MyRole implements AgentRole {
    HUNTER,
    GUARD,
    SCOUT,
}

Assigning roles across a group

RoleSelector<E, R> handles the mechanical bookkeeping, who currently holds which role, and honoring a minimum hold duration so roles don't flicker every tick. It has no opinion on how roles should be scored or distributed; you supply that as a scorer function.

var roleSelector = new RoleSelector<MyEntity, MyRole>(200); // minHoldDuration in ticks

// One-off, ignoring hold duration:
roleSelector.assign(agent, MyRole.GUARD, currentTick);

// Respecting hold duration, no-ops if not yet eligible:
boolean assigned = roleSelector.tryAssign(agent, MyRole.HUNTER, currentTick);

// Re-score a whole group at once, only touching agents eligible for reassignment:
roleSelector.reassignEligible(hiveMembers, member -> scoreBestRoleFor(member), currentTick);

var current = roleSelector.currentRole(agent); // null if never assigned
roleSelector.clear(agent);

RoleAssignment<R> is the underlying immutable record (role, assignedTick) with canReassign(currentTick, minHoldDuration).

Store the chosen role on the agent's blackboard under a mod-defined key if actions/tree nodes need to read it (AzureCortex doesn't reserve a CommonBlackboardKeys entry for this, since role concepts are entirely mod-specific).

Next

  • GOAP Planning, where role-based score biasing typically plugs in (inside your GoalPlanner).
  • Core Concepts, how Blackboard (per-tick) differs from AgentMemory (cross-tick, timestamped).

Clone this wiki locally