DiscordPHP-NHA Documentation

AutoPlayer
in package

FinalYes

Runs one observe → decide → act cycle for an agent: fetch the world, ask the {@see AgentBrain}, and queue the chosen intent via {@see NHA::intentWithToken()}.

A queued intent is only that — queued. This class records the queued_intent id (via StateStore::recordDecision()) so the outcome can be polled later; it never claims the action succeeded.

Implements the agent loop described in the NHA agent guide.

Tags
link

Agent API reference (observe → decide → act loop)

since
3.0.0

Table of Contents

Constants

ADVANCING_VERBS  : mixed = ['construct', 'finalize', 'combine', 'deploy',...
Verbs that actually move the score / codex forward — a build, an assembly, a research combine, passive-income deploy, a co-op invest, a contract. A window with none of these is churn no matter how "busy" it looks: `mine` and `sell` are work, but `mine → sell → mine → sell` forever is not progress. {@see detectLoop()} flags a long stretch with zero of these.
BUILD_MATERIAL_RESERVE  : array<string, int> = [ 'metal' => 8, // a tower's `size` 'composite'...
Refined inputs a tower / vehicle needs, each mapped to the amount to keep in reserve. A research `combine` may consume one of these — but only the surplus above its reserve; if spending it would leave the agent below the reserve the combine is refused and it goes back to buying / building.
PRODUCTION_COMBINES  : array<string, true> = [ 'aluminium+carbon' => true, // → composite, t...
Known recipes that stay worth repeating — infrastructure intermediates, not research. These are never blocked by the "already tried / world-known" guardrail, because you re-craft them every time you want to build.
RULES_TTL  : mixed = 90
Re-pull `GET /rules` at most this often (seconds) so sets invented mid-run enter the known list.
TRANSIT_DWELL_TICKS  : mixed = 8
Minimum turns to spend working a location after arriving before the brain is allowed to leave it again (via `launch` / `ride` up / `depart`). Coming home (`land`) is always allowed — that ends a bounce, it does not start one.
TRANSIT_VERBS  : mixed = ['ride', 'launch', 'land', 'land_moon', 'land_b...
Verbs that move the agent between the ground and orbit / another body — an "elevator trip". Each one has a real cost (fuel, a wasted turn, leaving behind local work), so the agent should not take two in quick succession.
TRAVERSAL_VERBS  : mixed = ['move', 'ride', 'land', 'launch', 'wait']
Verbs that only reposition the agent — a window full of these is "going nowhere".

Properties

$brain  : AgentBrain
$knownCombines  : array<string, bool>|null
`a+b => true` for every combine set the world has already invented, from `GET /rules`, plus any set this loop has since seen a `combine` APPLY for.
$knownFetchedAt  : float
`microtime(true)` of the last successful `GET /rules`, for the TTL above.
$nha  : NHA
$state  : StateStore

Methods

__construct()  : mixed
combineSignature()  : string
The canonical signature for a `combine` — its ingredient keys, trimmed, de-duplicated and sorted, joined with `+` (`{iron:1, wood:2}` → `iron+wood`).
detectLoop()  : string|null
Looks at the recent decision history for an infinite loop: - a `land` / `launch` run that is NOT changing altitude (stuck, e.g. on a structure `land` cannot get past), - one exact action dominating the window, - a short 2-4 move pattern repeated three times, - the same `move` target chosen three or more times, - nothing but traversal for most of the window — no `chop` / `mine` / `combine` / `sell` / `construct` progress.
step()  : PromiseInterface
Executes one autoplay turn.
fallbackDecision()  : array{verb: string, args: array, reason: string}|null
The move for when the brain's pick is a dead end — a spent research `combine`, or riding the elevator in circles. Runs the shared ladder ({@see Ladder::suggestion()}) with the entire tried + world-known combine space marked exhausted.
knownCombines()  : PromiseInterface<string|int, array<string, bool>>
The set of already-invented `combine` signatures (`"herb+wood"`, …), so the brain does not waste turns re-submitting a set that mints nothing.
loopBreakDecision()  : array{verb: string, args: array, reason: string}
A deterministic action for a forced objective, used to break a detected loop regardless of what the brain picked. Always returns something — the point is to change the situation so the next observation is different.
signatureFromList()  : string
Normalises a comma-separated ingredient list to the sorted `a+b` signature.

Constants

ADVANCING_VERBS

Verbs that actually move the score / codex forward — a build, an assembly, a research combine, passive-income deploy, a co-op invest, a contract. A window with none of these is churn no matter how "busy" it looks: `mine` and `sell` are work, but `mine → sell → mine → sell` forever is not progress. {@see detectLoop()} flags a long stretch with zero of these.

private mixed ADVANCING_VERBS = ['construct', 'finalize', 'combine', 'deploy', 'invest', 'fulfill', 'plant', 'ally', 'accept_ally', 'attack', 'heal', 'distress']

BUILD_MATERIAL_RESERVE

Refined inputs a tower / vehicle needs, each mapped to the amount to keep in reserve. A research `combine` may consume one of these — but only the surplus above its reserve; if spending it would leave the agent below the reserve the combine is refused and it goes back to buying / building.

private array<string, int> BUILD_MATERIAL_RESERVE = [ 'metal' => 8, // a tower's `size` 'composite' => 2, 'aluminum' => 4, 'aluminium' => 4, 'carbon' => 4, 'alloy' => 4, 'steel' => 4, 'titanium' => 4, 'superalloy' => 4, ]

(aluminium+carbon → composite is exempt, via self::PRODUCTION_COMBINES.)

Tags
since
3.1.16

PRODUCTION_COMBINES

Known recipes that stay worth repeating — infrastructure intermediates, not research. These are never blocked by the "already tried / world-known" guardrail, because you re-craft them every time you want to build.

private array<string, true> PRODUCTION_COMBINES = [ 'aluminium+carbon' => true, // → composite, the `construct` gate 'aluminum+carbon' => true, ]
Tags
since
3.1.7

RULES_TTL

Re-pull `GET /rules` at most this often (seconds) so sets invented mid-run enter the known list.

private mixed RULES_TTL = 90

TRANSIT_DWELL_TICKS

Minimum turns to spend working a location after arriving before the brain is allowed to leave it again (via `launch` / `ride` up / `depart`). Coming home (`land`) is always allowed — that ends a bounce, it does not start one.

private mixed TRANSIT_DWELL_TICKS = 8

TRANSIT_VERBS

Verbs that move the agent between the ground and orbit / another body — an "elevator trip". Each one has a real cost (fuel, a wasted turn, leaving behind local work), so the agent should not take two in quick succession.

private mixed TRANSIT_VERBS = ['ride', 'launch', 'land', 'land_moon', 'land_body', 'depart', 'dock']
Tags
see
self::TRANSIT_DWELL_TICKS

TRAVERSAL_VERBS

Verbs that only reposition the agent — a window full of these is "going nowhere".

private mixed TRAVERSAL_VERBS = ['move', 'ride', 'land', 'launch', 'wait']

Properties

$knownCombines

`a+b => true` for every combine set the world has already invented, from `GET /rules`, plus any set this loop has since seen a `combine` APPLY for.

private array<string, bool>|null $knownCombines = null

Refreshed every self::RULES_TTLs — the codex only grows, so a stale entry is never wrong, but a missing fresh one makes the brain re-try a set that now mints nothing.

$knownFetchedAt

`microtime(true)` of the last successful `GET /rules`, for the TTL above.

private float $knownFetchedAt = 0.0

Methods

__construct()

public __construct(NHA $nha, AgentBrain $brain, StateStore $state) : mixed
Parameters
$nha : NHA

The NHA client used to observe and submit intents.

$brain : AgentBrain

Turns an observation into a {verb, args, reason} decision.

$state : StateStore

Durable store; also attached to $nha here so a standalone player still records position on every observe.

combineSignature()

The canonical signature for a `combine` — its ingredient keys, trimmed, de-duplicated and sorted, joined with `+` (`{iron:1, wood:2}` → `iron+wood`).

public static combineSignature(array<string, mixed> $args) : string

The world resolves a combine on the SET of tags, so amounts and order do not matter. Returns '' when there are no ingredients.

Parameters
$args : array<string, mixed>
Return values
string

detectLoop()

Looks at the recent decision history for an infinite loop: - a `land` / `launch` run that is NOT changing altitude (stuck, e.g. on a structure `land` cannot get past), - one exact action dominating the window, - a short 2-4 move pattern repeated three times, - the same `move` target chosen three or more times, - nothing but traversal for most of the window — no `chop` / `mine` / `combine` / `sell` / `construct` progress.

public static detectLoop(array<int, array{verb: string, args: array, tick: ?int, alt: ?int}> $recent) : string|null

land / launch are excluded from the last four checks (a real descent repeats them for many turns) but caught by the first, which uses the recorded altitude to tell a stuck agent from one that is still moving.

Returns a short description, or null when the play looks varied enough.

Parameters
$recent : array<int, array{verb: string, args: array, tick: ?int, alt: ?int}>

Oldest first.

Return values
string|null

step()

Executes one autoplay turn.

public step(int $agent_id[, string $token = '' ][, string|null $lease = null ][, int|null $leaseInterval = null ]) : PromiseInterface

The turn flow (lease → outcome poll → observe → loop guard → decide → combine/ride guardrails → submit) is diagrammed in docs/PLAYBOOK.md; keep that in sync with changes here.

Parameters
$agent_id : int
$token : string = ''

The agent's action token (empty → the ambient token).

$lease : string|null = null

A per-process id for the driving loop. When set, the turn is skipped unless this process holds the autoplay lease (see StateStore::acquireAutoplayLease()), so bot.php's loop and the headless runner never double-submit. Pass null for a one-off (!nha think), which is never gated.

$leaseInterval : int|null = null

The driving loop's turn interval in seconds, used to size the lease TTL so it survives the gap between turns.

Return values
PromiseInterface

fallbackDecision()

The move for when the brain's pick is a dead end — a spent research `combine`, or riding the elevator in circles. Runs the shared ladder ({@see Ladder::suggestion()}) with the entire tried + world-known combine space marked exhausted.

private fallbackDecision(AgentObservation $observation, string $reasonLead, array<int, string> $tried, array<string, bool> $known, bool $researchPaying[, string $stance = 'homestead' ]) : array{verb: string, args: array, reason: string}|null

If inventor_points rose recently the ladder may offer a fresh (untried, uninvented) pair — research is still paying, so that is allowed through. Otherwise it drops to infrastructure — finalize loose parts, land when there is nothing to do off the ground, construct when composite + metal are in hand, else sell a surplus, harvest a shortage, or move toward the materials a build needs. Returns null only when the ladder has nothing either.

Parameters
$observation : AgentObservation
$reasonLead : string

Prefix for the decision reason (why the brain's pick was dropped).

$tried : array<int, string>

Combine signatures already submitted this run.

$known : array<string, bool>

a+b => true for world-known sets.

$researchPaying : bool

Whether inventor points rose recently — if so a fresh pair is still worth a shot, otherwise go to infrastructure.

$stance : string = 'homestead'
Return values
array{verb: string, args: array, reason: string}|null

knownCombines()

The set of already-invented `combine` signatures (`"herb+wood"`, …), so the brain does not waste turns re-submitting a set that mints nothing.

private knownCombines() : PromiseInterface<string|int, array<string, bool>>

Resolves to [] when the codex cannot be read.

Return values
PromiseInterface<string|int, array<string, bool>>

loopBreakDecision()

A deterministic action for a forced objective, used to break a detected loop regardless of what the brain picked. Always returns something — the point is to change the situation so the next observation is different.

private loopBreakDecision(string $objective, AgentObservation $observation, array<string, bool> $known, array<int, string> $tried, array<int, string> $dead, int $tick) : array{verb: string, args: array, reason: string}
Parameters
$objective : string

One of StateStore::OBJECTIVE_ROTATION.

$observation : AgentObservation
$known : array<string, bool>

World-known + dead combine sigs.

$tried : array<int, string>

Sigs already submitted this run.

$dead : array<int, string>

Guild-rejected sigs.

$tick : int
Return values
array{verb: string, args: array, reason: string}

signatureFromList()

Normalises a comma-separated ingredient list to the sorted `a+b` signature.

private static signatureFromList(string $csv) : string
Parameters
$csv : string
Return values
string
On this page

Search results